diff --git a/lib/MeasureUnit_ESP8266/Adc.cpp b/lib/MeasureUnit_ESP8266/Adc.cpp new file mode 100644 index 0000000..dc8129c --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Adc.cpp @@ -0,0 +1,43 @@ +#include "Adc.h" + +Adc::Adc() : _lastChannel(0), _adcSetting(0,0), _state(IDLING), _sampledValue(0), _numOfSamples(0), _elapsedTime(0) +{ + //Serial.println("Adc constructor called"); +} + +Adc::~Adc() +{ + +} + +void Adc::setAdcSetting(AdcSetting adcSetting) +{ + _adcSetting = adcSetting; +} + +AdcSetting Adc::getAdcSetting() +{ + return _adcSetting; +} + +boolean Adc::isSampleReady() +{ + return _state == RESULT_READY; +} + +double Adc::getQuantum() +{ + return _adcSetting.getQuantum(); +} + +double Adc::getSampleValue() +{ + double ret(0); + if(_state == RESULT_READY) + { + ret = _sampledValue; + _state = IDLING; + } + + return ret; +} diff --git a/lib/MeasureUnit_ESP8266/Adc.h b/lib/MeasureUnit_ESP8266/Adc.h new file mode 100644 index 0000000..c22f267 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Adc.h @@ -0,0 +1,42 @@ +#ifndef ADC_H +#define ADC_H +#include "AdcSetting.h" + +class Adc +{ + public: + virtual ~Adc() = 0; + + virtual void begin() = 0; + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true) = 0; + virtual double sampleValue() = 0; + virtual double sampleVoltage(int16_t channel, boolean sgl = true) = 0; + virtual double sampleVoltage() = 0; + + //Async methods + enum STATE {STARTED = 0, RESULT_READY, IDLING, SAMPLING}; + virtual void startSample(int16_t channel, boolean sgl = true) = 0; + virtual void startSample() = 0; + virtual double getSampleVoltage() = 0; + + boolean isSampleReady(); + double getSampleValue(); + //End of async methods + + void setAdcSetting(AdcSetting adcSetting); + AdcSetting getAdcSetting(); + protected: + Adc(); + + int16_t _lastChannel; + AdcSetting _adcSetting; + //Async part + STATE _state; + double _sampledValue; + uint8_t _numOfSamples; + unsigned long _elapsedTime; + private: +}; + +#endif //ADC_H diff --git a/lib/MeasureUnit_ESP8266/AdcSetting.cpp b/lib/MeasureUnit_ESP8266/AdcSetting.cpp new file mode 100644 index 0000000..d6dc318 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/AdcSetting.cpp @@ -0,0 +1,19 @@ +#include "AdcSetting.h" + +AdcSetting::AdcSetting(double vref, +uint8_t adcResolution, +uint8_t measureIteration, +uint16_t delayBetweenIteration) : _vref(vref), _adcResolution(adcResolution), _measureIteration(measureIteration), _delayBetweenIteration(delayBetweenIteration), _quantum(vref/(pow(2.0,(double) adcResolution)-1)) +{ + +} + +uint8_t AdcSetting::getMeasureIteration() +{ + return _measureIteration; +} + +double AdcSetting::getQuantum() +{ + return _quantum; +} diff --git a/lib/MeasureUnit_ESP8266/AdcSetting.h b/lib/MeasureUnit_ESP8266/AdcSetting.h new file mode 100644 index 0000000..5d7a36c --- /dev/null +++ b/lib/MeasureUnit_ESP8266/AdcSetting.h @@ -0,0 +1,25 @@ +#ifndef ADCSETTING_H +#define ADCSETTING_H +#include +#include + +class AdcSetting +{ + public: + AdcSetting(double vref, uint8_t adcResolution, uint8_t measureIteration = 5, uint16_t delayBetweenIteration = 5); + ~AdcSetting(){} + + uint8_t getMeasureIteration(); + uint16_t getDelayBetweenIteration(){ return _delayBetweenIteration;} + double getQuantum(); + double getVref(){return _vref;} + protected: + private: + double _vref; + uint8_t _adcResolution; + double _quantum; + uint8_t _measureIteration; + uint16_t _delayBetweenIteration; +}; + +#endif //ADCSETTING_H diff --git a/lib/MeasureUnit_ESP8266/Ads1115.cpp b/lib/MeasureUnit_ESP8266/Ads1115.cpp new file mode 100644 index 0000000..e6ba689 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Ads1115.cpp @@ -0,0 +1,127 @@ +#include "Ads1115.h" +#define ADS_DEBUG + +Ads1115::Ads1115() : ads1(0x48), ads2(0x49) +{ + #ifdef ADS_DEBUG + Serial.println("Ads1115 constructor called"); + #endif + //We set the adcs up: + ads1.setGain(GAIN_ONE); + ads2.setGain(GAIN_ONE); +} + +Ads1115::~Ads1115() +{ + +} + +void Ads1115::begin() +{ + #ifdef ADS_DEBUG + Serial.println("Ads1115 : begin"); + #endif + ads1.begin(); + ads2.begin(); +} + +double Ads1115::getQuantum() +{ + return 0.125; +} + +double Ads1115::sampleValue(int16_t channel, boolean sgl) +{ + double total(0); + for(int i(0); i < getAdcSetting().getMeasureIteration(); i++) + { + delay(getAdcSetting().getDelayBetweenIteration()); + total += getReading(channel, sgl); + } + + //We divide + total /= (double)getAdcSetting().getMeasureIteration(); + + //We return + return total; +} + +double Ads1115::sampleValue() +{ + return sampleValue(-1); +} + +double Ads1115::sampleVoltage(int16_t channel, boolean sgl) +{ + return sampleValue(channel, sgl)*0.125; +} + +double Ads1115::sampleVoltage() +{ + return sampleValue()*0.125; +} + +uint16_t Ads1115::getReading(int16_t channel, boolean sgl) +{ + int16_t chan(channel == -1 ? _lastChannel : channel); + + _lastChannel = chan > 8 ? 0 : chan; + + if(chan < 4) + { + return ads1.readADC_SingleEnded(chan); + } + else if(chan < 8) + { + return ads2.readADC_SingleEnded(chan - 4); + } + else return 0; +} + + +//Async part +void Ads1115::startSample(int16_t channel, boolean sgl) +{ + switch(_state) + { + case IDLING: + _state = SAMPLING; + _elapsedTime = millis(); + _sampledValue = 0.0; + _numOfSamples = 0; + //We set the last channel attribute + if(channel != -1) + { + _lastChannel = channel > 8 ? _lastChannel : channel; + } + + break; + case SAMPLING: + //If enough time elapsed, we can sample a value again + if(millis() - _elapsedTime > getAdcSetting().getDelayBetweenIteration()) + { + _sampledValue += getReading(channel, sgl); + _elapsedTime = millis(); + _numOfSamples ++; + } + + //All samples are done: + if(_numOfSamples == getAdcSetting().getMeasureIteration()) + { + _sampledValue /= (double)_numOfSamples; + _sampledValue += channel > 3 ? 82.0 : 0.0; + _state = RESULT_READY; + } + break; + } +} + +void Ads1115::startSample() +{ + startSample(-1); +} + +double Ads1115::getSampleVoltage() +{ + return getSampleValue() * 0.125; +} diff --git a/lib/MeasureUnit_ESP8266/Ads1115.h b/lib/MeasureUnit_ESP8266/Ads1115.h new file mode 100644 index 0000000..b215d37 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Ads1115.h @@ -0,0 +1,31 @@ +#ifndef ADS1115_H +#define ADS1115_H +#include "Adc.h" +#include +#include + +class Ads1115 : public Adc +{ + public: + Ads1115(); + ~Ads1115(); + + virtual void begin(); + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true); + virtual double sampleValue(); + virtual double sampleVoltage(int16_t channel, boolean sgl = true); + virtual double sampleVoltage(); + + //Async methods + virtual void startSample(int16_t channel, boolean sgl = true); + virtual void startSample(); + virtual double getSampleVoltage(); + //End of async methods + protected: + private: + uint16_t getReading(int16_t channel = -1, boolean sgl = true); + Adafruit_ADS1115 ads1, ads2; +}; + +#endif //ADS1115_H diff --git a/lib/MeasureUnit_ESP8266/Ads1115V2.cpp b/lib/MeasureUnit_ESP8266/Ads1115V2.cpp new file mode 100644 index 0000000..8e7d7db --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Ads1115V2.cpp @@ -0,0 +1,134 @@ +#include "Ads1115V2.h" +#define ADSV2_DEBUG + +Ads1115V2::Ads1115V2() : ads1(ADS1115_ADDRESS_ADDR_GND), ads2(ADS1115_ADDRESS_ADDR_VDD) +{ + #ifdef ADSV2_DEBUG + Serial.println("Ads1115 constructor called"); + #endif +} + +Ads1115V2::~Ads1115V2() +{ + +} + +void Ads1115V2::begin() +{ + #ifdef ADSV2_DEBUG + Serial.println("Ads1115V2 : begin"); + #endif + Wire.begin(); + //We set the adcs up: + ads1.initialize(); + ads2.initialize(); + ads1.setMode(ADS1115_MODE_SINGLESHOT); + ads2.setMode(ADS1115_MODE_SINGLESHOT); + ads1.setRate(ADS1115_RATE_250); + ads2.setRate(ADS1115_RATE_250); + ads1.setGain(ADS1115_PGA_4P096); + ads2.setGain(ADS1115_PGA_4P096); +} + +double Ads1115V2::getQuantum() +{ + return 0.125; +} + +double Ads1115V2::sampleValue(int16_t channel, boolean sgl) +{ + double total(0); + for(int i(0); i < getAdcSetting().getMeasureIteration(); i++) + { + delay(getAdcSetting().getDelayBetweenIteration()); + total += getReading(channel, sgl); + } + + //We divide + total /= (double)getAdcSetting().getMeasureIteration(); + + //We return + return total; +} + +double Ads1115V2::sampleValue() +{ + return sampleValue(-1); +} + +double Ads1115V2::sampleVoltage(int16_t channel, boolean sgl) +{ + return sampleValue(channel, sgl)*0.125; +} + +double Ads1115V2::sampleVoltage() +{ + return sampleValue()*0.125; +} + +uint16_t Ads1115V2::getReading(int16_t channel, boolean sgl) +{ + int16_t chan(channel == -1 ? _lastChannel : channel); + + _lastChannel = chan > 8 ? 0 : chan; + + if(chan < 4) + { + ads1.setMultiplexer(chan+4); + return ads1.getConversion(true); + } + else if(chan < 8) + { + ads2.setMultiplexer(chan); + return ads2.getConversion(true); + } + else return 0; +} + + +//Async part +void Ads1115V2::startSample(int16_t channel, boolean sgl) +{ + switch(_state) + { + case IDLING: + _state = SAMPLING; + _elapsedTime = millis(); + _sampledValue = 0.0; + _numOfSamples = 0; + //We set the last channel attribute + if(channel != -1) + { + _lastChannel = channel > 8 ? _lastChannel : channel; + } + + break; + case SAMPLING: + //If enough time elapsed, we can sample a value again + if(millis() - _elapsedTime > getAdcSetting().getDelayBetweenIteration()) + { + _sampledValue += getReading(channel, sgl); + _elapsedTime = millis(); + _numOfSamples ++; + } + + //All samples are done: + if(_numOfSamples == getAdcSetting().getMeasureIteration()) + { + _sampledValue /= (double)_numOfSamples; + _sampledValue += channel > 3 ? 82.0 : 0.0; + _state = RESULT_READY; + } + break; + } +} + +void Ads1115V2::startSample() +{ + startSample(-1); +} + +double Ads1115V2::getSampleVoltage() +{ + return getSampleValue() * 0.125; +} diff --git a/lib/MeasureUnit_ESP8266/Ads1115V2.h b/lib/MeasureUnit_ESP8266/Ads1115V2.h new file mode 100644 index 0000000..a80ea9b --- /dev/null +++ b/lib/MeasureUnit_ESP8266/Ads1115V2.h @@ -0,0 +1,31 @@ +#ifndef ADS1115V2_H +#define ADS1115V2_H +#include "Adc.h" +#include +#include + +class Ads1115V2 : public Adc +{ + public: + Ads1115V2(); + ~Ads1115V2(); + + virtual void begin(); + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true); + virtual double sampleValue(); + virtual double sampleVoltage(int16_t channel, boolean sgl = true); + virtual double sampleVoltage(); + + //Async methods + virtual void startSample(int16_t channel, boolean sgl = true); + virtual void startSample(); + virtual double getSampleVoltage(); + //End of async methods + protected: + private: + uint16_t getReading(int16_t channel = -1, boolean sgl = true); + ADS1115 ads1, ads2; +}; + +#endif //ADS1115V2_H diff --git a/lib/MeasureUnit_ESP8266/MeasureUnit.cpp b/lib/MeasureUnit_ESP8266/MeasureUnit.cpp new file mode 100644 index 0000000..c5e9241 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/MeasureUnit.cpp @@ -0,0 +1,375 @@ +#include "MeasureUnit.h" +//#define DEBUG + +MeasureUnit::MeasureUnit(uint8_t *analogInput, +uint16_t thermistorCount, +uint64_t precResistor, +ThermistorSetting thermistorSetting, +Adc &adc) : _analogInput(analogInput), +_thermistorCount(thermistorCount), +_precResistor(precResistor), +_thermistorSetting(thermistorSetting), +_adc(adc), +_globalOffset(0), +_error(OK), +_state(IDLING), +_channel(0), +_offsetComputeIte(7), +_offsetCounter(7), +_courant(0.0), +_tension(0.0), +_triggerLevelOff(false) +{ + //Allocation dynamique des différent tableaux + _temperatures = (double*) calloc(_thermistorCount, sizeof(double)); + _rOffsetMap = (double*) calloc(_thermistorCount, sizeof(double)); + _resistanceMap = (double*) malloc(_thermistorCount * sizeof(double)); + _rOffsetBuffer = (double*) malloc(_thermistorCount * sizeof(double)); + + if(_temperatures == NULL || _rOffsetMap == NULL || _resistanceMap == NULL || _rOffsetBuffer == NULL) + { + _error = MALLOC_ERR; + _temperatures != NULL ? free(_temperatures):(void)_temperatures; + _rOffsetMap != NULL ? free(_rOffsetMap):(void)_rOffsetMap; + _resistanceMap != NULL ? free(_resistanceMap):(void)_resistanceMap; + _rOffsetBuffer != NULL ? free(_rOffsetBuffer):(void)_rOffsetBuffer; + + _temperatures = NULL; + _rOffsetMap = NULL; + _resistanceMap = NULL; + _rOffsetBuffer = NULL; + } + + _adc.begin(); +} + +MeasureUnit::~MeasureUnit() +{ + if(_error != MALLOC_ERR) + { + free(_temperatures); + free(_rOffsetMap); + free(_resistanceMap); + free(_rOffsetBuffer); + } +} + +void MeasureUnit::run() +{ + switch(_state) + { + case MEASURING: + _adc.startSample(_channel); + + if(_channel == 0) //Calcule du courant + { + if(_adc.isSampleReady()) + { + _tension = _adc.getSampleVoltage(); + _courant = _tension / (double) _precResistor; + #ifdef DEBUG + Serial.print("Tension prec : ");Serial.println(_tension); + #endif + _channel++; + } + } + else //Calcule des niveaux de tensions + { + if(_adc.isSampleReady()) + { + _resistanceMap[_channel-1] = _adc.getSampleVoltage(); + #ifdef DEBUG + Serial.print("Tension thermistances : ");Serial.println(_resistanceMap[_channel-1]); + #endif + _channel++; + } + } + + //Fin de la partie d'acquisition + if(_channel == _thermistorCount) + { + _state = COMPUTING; + _resistanceMap[_channel-1] = _adc.getAdcSetting().getVref(); + } + break; + case COMPUTING : + //Ici nous calculons les temperatures + for(int i(_thermistorCount-1); i > 0; i--) + { + //Calcule de delta : + _resistanceMap[i] -= _resistanceMap[i-1]; + #ifdef DEBUG + Serial.printf("Debug voltage delta : %u -> ",i);Serial.println(_resistanceMap[i]); + #endif + } + + //Ne pas oublier de déduire la chute de tension de la resistance de precision pour la première thermistance + _resistanceMap[0] -= _tension; + #ifdef DEBUG + Serial.printf("Debug voltage delta : 0 -> ");Serial.println(_resistanceMap[0]); + #endif + + for(int i(0); i < _thermistorCount; i++) + { + //3) Nous en déduisons la résistance + //Serial.print("Resistance ");Serial.print(i);Serial.print(" ");Serial.println(_resistanceMap[i]); + _resistanceMap[i] /= _courant; + //4) Nous en déduisons la temperature + _temperatures[i] = computeTemperature(_thermistorSetting.getBeta(), _resistanceMap[i], _thermistorSetting.getRat25()); + + //On effectue un étalonnage + if(_triggerLevelOff) + { + double averageTemp(0); + //We reset the offset + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = 0; + } + + for(int i(0); i < _thermistorCount; i++) + { + averageTemp += _temperatures[i]; + } + + averageTemp /= (double)_thermistorCount; + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _temperatures[i]; + } + _triggerLevelOff = false; + } + + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + #ifdef DEBUG_TEMP + Serial.print("Temperature ");Serial.print(i);Serial.print(" : ");Serial.println(_temperatures[i]); + #endif + } + _state = MEASUREMENT_READY; + break; + } + + if(_offsetCounter < _offsetComputeIte && isMeasurementReady()) + { + //We reset the offset array + if(_offsetCounter == 0) + { + Serial.printf("Initiating average \n"); + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] = 0; + _rOffsetMap[i] = 0; + } + } + else if(_offsetCounter == _offsetComputeIte-1) + { + double averageTemp(0); + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] += _temperatures[i]; + } + //We compute the average for each thermistor: + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] /= _offsetCounter; + averageTemp += _rOffsetBuffer[i]; + } + + averageTemp /= _thermistorCount; + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _rOffsetBuffer[i]; + } + + Serial.println("Offset done"); + Serial.print("|"); + for(int i(0); i < _thermistorCount; i++) + { + if(i != 7) + { + Serial.print(" ");Serial.print(_rOffsetMap[i],2);Serial.print(" |"); + } + else + { + Serial.print(" ");Serial.print(_rOffsetMap[i],2);Serial.print(" |"); + } + } + Serial.println(""); + } + else + { + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] += _temperatures[i]; + } + } + + _offsetCounter++; + } +} + +void MeasureUnit::setOffsetIteration(uint8_t iteration) +{ + _offsetComputeIte = iteration; + _offsetCounter = iteration; +} + +void MeasureUnit::startOffsetComputing() +{ + _offsetCounter = 0; +} + +/** + * Methode permettant d'effectuer les mesures de température et de les récupérer + */ +double *MeasureUnit::getTemperatures() +{ + double courant(0), rPrecTension(0); + //1) Nous calculons le courant présent dans la branche grace à la résistance de précision + #ifdef DEBUG + Serial.println("-------------"); + #endif + rPrecTension = _adc.sampleVoltage(0); + #ifdef DEBUG + Serial.println("-------------"); + Serial.print("R prec voltage mV : ");Serial.println(rPrecTension,6); + #endif + + courant = rPrecTension / (double) _precResistor; + #ifdef DEBUG + Serial.print("R prec current mA : ");Serial.println(courant,6); + #endif + + + //2) Nous calculons le delta de tensions pour chaque thermistances + for(int i(1); i < _thermistorCount; i++) + { + _resistanceMap[i-1] = _adc.sampleVoltage(_analogInput[i]); + #ifdef DEBUG + Serial.print("Voltage steps ");Serial.print(i-1);Serial.print(" : ");Serial.println(_resistanceMap[i-1]); + #endif + } + _resistanceMap[7] = _adc.getAdcSetting().getVref(); + #ifdef DEBUG + Serial.print("Voltage steps 7 : ");Serial.println(_resistanceMap[7]); + #endif + + for(int i(_thermistorCount-1); i > 0; i--) + { + //Calcule de delta : + _resistanceMap[i] -= _resistanceMap[i-1]; + #ifdef DEBUG + Serial.print("Debug voltage delta : ");Serial.println(_resistanceMap[i]); + #endif + } + + //Ne pas oublier de déduire la chute de tension de la resistance de precision pour la première thermistance + _resistanceMap[0] -= rPrecTension; + Serial.printf("Debug voltage delta : 0 -> ");Serial.println(_resistanceMap[0]); + + for(int i(0); i < _thermistorCount; i++) + { + //3) Nous en déduisons la résistance + //Serial.print("Resistance ");Serial.print(i);Serial.print(" ");Serial.println(_resistanceMap[i]); + _resistanceMap[i] /= courant; + //4) Nous en déduisons la temperature + _temperatures[i] = computeTemperature(_thermistorSetting.getBeta(), _resistanceMap[i], _thermistorSetting.getRat25()); + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + #ifdef DEBUG_TEMP + Serial.print("Temperature ");Serial.print(i);Serial.print(" : ");Serial.println(_temperatures[i]); + #endif + } + + return _temperatures; +} + +double MeasureUnit::computeTemperature(double beta, double resistance, double rAt25) +{ + return (((25.0+273.15) * beta) / (beta + (25.0+273.15)*log(resistance / rAt25))) - 273.15; +} + +void MeasureUnit::setGlobalTempOffset(double offset) +{ + _globalOffset = offset; +} + +double MeasureUnit::getGlobalTempOffset() +{ + return _globalOffset; +} + +/** + * Cette méthode permet de calibrer toutes les temperatures en faisans la moyenne et appliquant un offset individuel + */ +void MeasureUnit::levelTemperaturesOff() +{ + double averageTemp(0); + //We reset the offset + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = 0; + } + + getTemperatures(); + + for(int i(0); i < _thermistorCount; i++) + { + averageTemp += _temperatures[i]; + } + + averageTemp /= _thermistorCount; + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _temperatures[i]; + } +} + +double *MeasureUnit::getROffsetMap() +{ + return _rOffsetMap; +} + +boolean MeasureUnit::startTemperatureMeasurement() +{ + + if(_state == IDLING) + { + _state = MEASURING; + _channel = 0; + return true; + } + + return false; +} + +void MeasureUnit::levelAsyncTemperaturesOff() +{ + _triggerLevelOff = true; +} + +boolean MeasureUnit::isMeasurementReady() +{ + return _state == MEASUREMENT_READY; +} + +double *MeasureUnit::getAsyncTemperatures() +{ + double *p(NULL); + + if(_state == MEASUREMENT_READY) + { + p = _temperatures; + _state = IDLING; + } + + return p; +} + +void MeasureUnit::init() +{ + Serial.println(computeTemperature(3380, 18000, 10000)); + Serial.println(computeTemperature(3380, 11700, 10000)); +} diff --git a/lib/MeasureUnit_ESP8266/MeasureUnit.h b/lib/MeasureUnit_ESP8266/MeasureUnit.h new file mode 100644 index 0000000..6c61bba --- /dev/null +++ b/lib/MeasureUnit_ESP8266/MeasureUnit.h @@ -0,0 +1,57 @@ +#ifndef MEASUREUNIT_H +#define MEASUREUNIT_H +#include "Ads1115.h" +#include "ThermistorSetting.h" + +//#define DEBUG_TEMP + +class MeasureUnit +{ + public: + enum ERROR {OK = 0, MALLOC_ERR = 1}; + MeasureUnit(uint8_t *analogInput, uint16_t thermistorCount, uint64_t precResistor, ThermistorSetting thermistorSetting, Adc &adc); + ~MeasureUnit(); + void init(); + void run(); + void startOffsetComputing(); + void setOffsetIteration(uint8_t iteration); + void setGlobalTempOffset(double offset); + void levelTemperaturesOff(); + double getGlobalTempOffset(); + double *getTemperatures(); + double *getROffsetMap(); + + //Async methods + enum STATE {IDLING, MEASURING, COMPUTING, MEASUREMENT_READY}; + boolean startTemperatureMeasurement(); + boolean isMeasurementReady(); + double *getAsyncTemperatures(); + void levelAsyncTemperaturesOff(); + //End of assync methods + + ERROR getError(){return _error;} + + protected: + private: + double computeTemperature(double beta, double resistance, double rAt25); + + double _globalOffset; //Correspond à l'offset global nécessaire afin d'avoir une température qui corresponde à la réalité + double *_temperatures; //Tableau contenant toutes les températures + double *_rOffsetMap,*_rOffsetBuffer; //Tableau qui contient les offsets individuels pour chaque thermistance + double *_resistanceMap; //Tableau qui contient les resistances associées aux thermistances (pour debug seulement) + uint8_t *_analogInput; //Pointeur qui garde l'adresse du tableau contenant le nom des entrées analogiques + uint16_t _thermistorCount; + uint64_t _precResistor; + ERROR _error; + + Adc &_adc; + ThermistorSetting _thermistorSetting; + + //Async part + STATE _state; + uint8_t _channel, _offsetComputeIte,_offsetCounter; + double _courant, _tension; + boolean _triggerLevelOff; //Attribut permettant de savoir si un étalonnage a été demandé +}; + +#endif //MEASUREUNIT_H diff --git a/lib/MeasureUnit_ESP8266/MeasureUnit_ESP8266.ino b/lib/MeasureUnit_ESP8266/MeasureUnit_ESP8266.ino new file mode 100644 index 0000000..a75b237 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/MeasureUnit_ESP8266.ino @@ -0,0 +1,201 @@ +/** + * Cet exemple correspond à l'application de test de la bibliothèque MeasureUnit qui + * permet de calculer la température qui est fonction de la résistance d'une matrice de thermistance + * + * Anatole SCHRAMM-HENRY + * 17/12/2019 + */ +#include +#include +#include +#include "PayloadFormatter.h" +#include "LoRaRadio.h" +#include "ThermistorSetting.h" +#include "AdcSetting.h" +#include "STS21.h" + +//#define RADIO_ENABLED +#define PUSH_BUTTON 0 + +uint8_t analogInput[] = {0,1,2,3,4,5,6,7}; +double tempArray[8] = {21.58,21.65,21.54,21.48,21.68,21.75,21.54,21.59}; + +/* + * Liste des offsets trouvés + * | -0.49 | 0.36 | -0.29 | 0.38 | 0.44 | -0.35 | -0.21 | 0.14 | + * | -0.72 | 0.07 | -0.52 | -0.01 | 2.38 | -0.65 | -0.44 | -0.11 | + * | -0.99 | -0.06 | -0.74 | 2.24 | 0.73 | -0.86 | -0.68 | 0.35 | + * | -0.53 | -0.49 | -0.27 | 1.17 | 0.07 | 0.14 | -0.02 | -0.08 | + * | -0.62 | -0.73 | 1.58 | 0.42 | -0.27 | 0.09 | -0.25 | -0.21 | + + * + */ + +//Objet de calcule de la temperature +ThermistorSetting thermistorSetting(3380, 10000); +//ThermistorSetting thermistorSetting(3650, 470); +//AdcSetting adcSetting(3300.0, 12, 310, 3); +AdcSetting adcSetting(3310, 15, 6, 10); +//MeasureUnit measureUnit(analogInput, 8, 99, thermistorSetting, adc); +//Objet de création des trames LoRa +PayloadFormatter payloadFormatter(2,4); + +RTC_DS1307 rtc; +DateTime payloadDate; +STS21 sts21; + +boolean data(false); +uint8_t *payload(NULL), _timeCounter(0), size(0), _channel(0); +boolean calibrer(false); +unsigned long _time(0); +/* + * Radio Part +*/ +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static u1_t NWKSKEY[16] = { 0x1F, 0x9E, 0xE2, 0x7A, 0xC8, 0xBA, 0xE8, 0xEA, 0xF5, 0xC2, 0x5E, 0x47, 0x5D, 0xE0, 0x77, 0x55 }; +static u1_t APPSKEY[16] = { 0x3B, 0x89, 0x86, 0x96, 0xBB, 0xAA, 0x38, 0x1E, 0x1F, 0xC4, 0xAD, 0x03, 0xEF, 0x3F, 0x56, 0x12 }; +static u4_t DEVADDR = 0x260113D3;//0x03FF0001 ; // <-- Change this address for every node! + +u1_t dio[3] = {15,3,LMIC_UNUSED_PIN}; +PinMap pinMap(2, LMIC_UNUSED_PIN, 0, dio); +LoRaRadio radio(pinMap); + +void downlinkHandler(u1_t length, u1_t dataBeg, u1_t *data) +{ + Serial.println("Downlink received : "); + for(uint8_t i(0); i < length; i++) + { + Serial.printf("%u -> %d\n",i,data[dataBeg + i]); + } + Serial.println(); + + //Action en fonction de l'octet de commande + switch(data[dataBeg+0]) + { + case 0x01://Mise à jour de l'heure + //Octets suivants: + //2 jour 3 mois 4 année 5 heures 6 minutes + if(length == 6) + { + Serial.printf("dd: %u, m: %u, yyyy: %d, hh: %u, mm: %u\n", data[dataBeg+1], data[dataBeg+2], data[dataBeg+3]+2000, data[dataBeg+4], data[dataBeg+5]); + } + else + Serial.println("Action réglage RTC : paramètres insuffisants"); + break; + case 0x02: + /*memcpy(screenTxt,(data+dataBeg+1), length-1); + screenTxt[length-1] = '\0'; + display.stopscroll(); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setCursor(0,15); + display.setTextSize(2); + display.print(screenTxt); + display.display(); + display.startscrollleft(0,16);*/ + break; + default: + Serial.println("Action inconnue"); + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println("Start setup"); + WiFi.mode(WIFI_OFF); + //Partie concernant l'initialisation de la radio + #ifdef RADIO_ENABLED + radio.init(); + radio.setTTNSession(0x1, DEVADDR, NWKSKEY, APPSKEY); + radio.setRadioEUChannels(); + /* + * La directive setMCUClockError() permet de laisser une fenêtre plus grande pour le slot de + * réception (Downlink). En effet ce slot doit durer 2 secondes et il peut durer moins en raison + * d'imprécisions d'horloge. + */ + //radio.setMCUClockError(50); + radio.setDownlinkHandler(&(downlinkHandler)); + #endif + _time = millis(); + + if(rtc.begin()) + Serial.println("RTC Ok!"); + else + Serial.println("RTC Fail!"); + + if(sts21.begin()) + { + Serial.println("Sensor present !"); + sts21.setResolution(STS21::RES_14); + } + else + Serial.println("Sensor missing !"); + + Serial.println("End setup"); + Serial.println("| T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 |"); + +} + +void loop() { + //Version asynchrone : + + //On peut tester si la conversion est terminée avec : + + //measureUnit.getAsyncTemperatures() renvoie NULL si la recupération de la température n'est pas terminée + //tempArray = //measureUnit.getAsyncTemperatures(); + double temp = sts21.getTemperature(); + if(tempArray != NULL) + { + Serial.print("|"); + for(int i(0); i < 8; i++) + { + if(i != 7) + { + Serial.print(" ");Serial.print(tempArray[i],2);Serial.print(" |"); + } + else + { + Serial.print(" ");Serial.print(tempArray[i],2);Serial.print(" |"); + } + } + + //On affiche la trame associée: + payloadFormatter.startSession(1); + payloadDate = rtc.now(); + size = payloadFormatter.buildPayload(&payload, &payloadDate, 22.5,tempArray); + if(size != 0) + { + //Serial.print("LoRa packet --> ");Serial.print("size : ");Serial.print(size);Serial.println(" bytes"); + for(int i(0); i < size; i++) + { + payload[i] <= 0x0F ? Serial.print("0") : Serial.print(""); Serial.print(payload[i], HEX); Serial.print(" "); + } + Serial.printf("|%u-%u-%u %u:%u ext temp : %.2f \n", payloadDate.day(),payloadDate.month(),payloadDate.year(),payloadDate.hour(),payloadDate.minute(), temp); + } + else + Serial.print("Failed to build LoRa packet"); + + payloadFormatter.endSession(); + + #ifdef RADIO_ENABLED + if(_timeCounter == 30 && size != 0) + { + _timeCounter = 0; + Serial.printf("Sending data\n"); + radio.send(1, payload, size); + } + + _timeCounter++; + delay(1000); + #endif + } + + //On effectue la calibration + #ifdef RADIO_ENABLED + radio.run(); + #endif +} diff --git a/lib/MeasureUnit_ESP8266/ThermistorSetting.cpp b/lib/MeasureUnit_ESP8266/ThermistorSetting.cpp new file mode 100644 index 0000000..af5f863 --- /dev/null +++ b/lib/MeasureUnit_ESP8266/ThermistorSetting.cpp @@ -0,0 +1,21 @@ +#include "ThermistorSetting.h" + +ThermistorSetting::ThermistorSetting(uint16_t beta, uint64_t rAt25) : _beta(beta), _rAt25(rAt25) +{ + +} + +ThermistorSetting::~ThermistorSetting() +{ + +} + +uint16_t ThermistorSetting::getBeta() +{ + return _beta; +} + +uint64_t ThermistorSetting::getRat25() +{ + return _rAt25; +} diff --git a/lib/MeasureUnit_ESP8266/ThermistorSetting.h b/lib/MeasureUnit_ESP8266/ThermistorSetting.h new file mode 100644 index 0000000..f6e205d --- /dev/null +++ b/lib/MeasureUnit_ESP8266/ThermistorSetting.h @@ -0,0 +1,18 @@ +#ifndef THERMISTORSETTING_H +#define THERMISTORSETTING_H +#include //Necessaire afin d'avoir les types : uintxx_t + +class ThermistorSetting +{ + public: + ThermistorSetting(uint16_t beta, uint64_t rAt25); + ~ThermistorSetting(); + uint16_t getBeta(); + uint64_t getRat25(); + protected: + private: + uint16_t _beta; + uint64_t _rAt25; +}; + +#endif //THERMISTORSETTING_H diff --git a/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.cpp b/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.cpp new file mode 100644 index 0000000..d6dc318 --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.cpp @@ -0,0 +1,19 @@ +#include "AdcSetting.h" + +AdcSetting::AdcSetting(double vref, +uint8_t adcResolution, +uint8_t measureIteration, +uint16_t delayBetweenIteration) : _vref(vref), _adcResolution(adcResolution), _measureIteration(measureIteration), _delayBetweenIteration(delayBetweenIteration), _quantum(vref/(pow(2.0,(double) adcResolution)-1)) +{ + +} + +uint8_t AdcSetting::getMeasureIteration() +{ + return _measureIteration; +} + +double AdcSetting::getQuantum() +{ + return _quantum; +} diff --git a/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.h b/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.h new file mode 100644 index 0000000..5d7a36c --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/AdcSetting.h @@ -0,0 +1,25 @@ +#ifndef ADCSETTING_H +#define ADCSETTING_H +#include +#include + +class AdcSetting +{ + public: + AdcSetting(double vref, uint8_t adcResolution, uint8_t measureIteration = 5, uint16_t delayBetweenIteration = 5); + ~AdcSetting(){} + + uint8_t getMeasureIteration(); + uint16_t getDelayBetweenIteration(){ return _delayBetweenIteration;} + double getQuantum(); + double getVref(){return _vref;} + protected: + private: + double _vref; + uint8_t _adcResolution; + double _quantum; + uint8_t _measureIteration; + uint16_t _delayBetweenIteration; +}; + +#endif //ADCSETTING_H diff --git a/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.cpp b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.cpp new file mode 100644 index 0000000..7e5f523 --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.cpp @@ -0,0 +1,183 @@ +#include "MeasureUnit.h" +//#define DEBUG + +MeasureUnit::MeasureUnit(uint16_t thermistorCount, +uint64_t precResistor, +ThermistorSettings thermistorSettings, +LTC2439 &adc) : _thermistorCount(thermistorCount), +_precResistor(precResistor), +_thermistorSettings(thermistorSettings), +_adc(adc), +_globalOffset(0), +_error(OK), +_state(IDLING), +_channel(0), +_offsetComputeIte(7), +_offsetCounter(7), +_courant{0.0, 0.0}, +_triggerLevelOff(false) +{ + //Allocation dynamique des différent tableaux + _temperatures = (double*) calloc(_thermistorCount, sizeof(double)); + _rOffsetMap = (double*) calloc(_thermistorCount, sizeof(double)); + _resistanceMap = (double*) malloc(_thermistorCount * sizeof(double)); + _rOffsetBuffer = (double*) malloc(_thermistorCount * sizeof(double)); + + if(_temperatures == NULL || _rOffsetMap == NULL || _resistanceMap == NULL || _rOffsetBuffer == NULL) + { + _error = MALLOC_ERR; + _temperatures != NULL ? free(_temperatures):(void)_temperatures; + _rOffsetMap != NULL ? free(_rOffsetMap):(void)_rOffsetMap; + _resistanceMap != NULL ? free(_resistanceMap):(void)_resistanceMap; + _rOffsetBuffer != NULL ? free(_rOffsetBuffer):(void)_rOffsetBuffer; + + _temperatures = NULL; + _rOffsetMap = NULL; + _resistanceMap = NULL; + _rOffsetBuffer = NULL; + } +} + +MeasureUnit::~MeasureUnit() +{ + if(_error != MALLOC_ERR) + { + free(_temperatures); + free(_rOffsetMap); + free(_resistanceMap); + free(_rOffsetBuffer); + } +} + +void MeasureUnit::run() +{ + switch(_state) + { + case MEASURING: + _adc.startAsyncSample(_channel); + + if(_adc.asyncResultAvailable()) + { + _resistanceMap[_channel] = _adc.convertToVoltage(_adc.getAsyncValue()); + #ifdef DEBUG + Serial.printf("Voltage %u : %.2f\n", _channel, _resistanceMap[_channel]); + #endif + _channel++; + } + + //Fin de la partie d'acquisition + if(_channel == _thermistorCount) + { + _state = COMPUTING; + } + break; + case COMPUTING : + //Ici nous calculons les temperatures des thermistances (8 à la fois) + _courant[0] = _resistanceMap[0] / (double) _precResistor; + for(int i(0); i < 8; i++) + { + //Calcule de delta : + if(i < 7) + _resistanceMap[i] = _resistanceMap[i+1] - _resistanceMap[i]; + else + _resistanceMap[i] = _adc.getVref() - _resistanceMap[i]; + #ifdef DEBUG + Serial.printf("Debug voltage delta : %u -> %.2f\n",i,_resistanceMap[i]); + #endif + } + + _courant[1] = _resistanceMap[8] / (double) _precResistor; + for(int i(8); i < 16; i++) + { + //Calcule de delta : + if(i < 15) + _resistanceMap[i] = _resistanceMap[i+1] - _resistanceMap[i]; + else + _resistanceMap[i] = _adc.getVref() - _resistanceMap[i]; + #ifdef DEBUG + Serial.printf("Debug voltage delta : %u -> %.2f\n",i,_resistanceMap[i]); + #endif + } + + //Calcule de la température (8 à la fois) : + for(int i(0); i < 8; i++) + { + _resistanceMap[i] /= _courant[0]; + _temperatures[i] = computeTemperature(_thermistorSettings.getBeta(), _resistanceMap[i], _thermistorSettings.getRat25()); + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + + #ifdef DEBUG + Serial.printf("Temperature %u -> %.2f\n", i, _temperatures[i]); + #endif + } + + for(int i(8); i < 16; i++) + { + _resistanceMap[i] /= _courant[1]; + _temperatures[i] = computeTemperature(_thermistorSettings.getBeta(), _resistanceMap[i], _thermistorSettings.getRat25()); + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + + #ifdef DEBUG + Serial.printf("Temperature %u -> %.2f\n", i, _temperatures[i]); + #endif + } + _state = MEASUREMENT_READY; + break; + } +} + +double MeasureUnit::computeTemperature(double beta, double resistance, double rAt25) +{ + return (((25.0+273.15) * beta) / (beta + (25.0+273.15)*log(resistance / rAt25))) - 273.15; +} + +void MeasureUnit::setGlobalTempOffset(double offset) +{ + _globalOffset = offset; +} + +double MeasureUnit::getGlobalTempOffset() +{ + return _globalOffset; +} + +double *MeasureUnit::getROffsetMap() +{ + return _rOffsetMap; +} + +boolean MeasureUnit::startTemperatureMeasurement() +{ + + if(_state == IDLING) + { + _state = MEASURING; + _channel = 0; + return true; + } + + return false; +} + +boolean MeasureUnit::isMeasurementReady() +{ + return _state == MEASUREMENT_READY; +} + +double *MeasureUnit::getAsyncTemperatures() +{ + double *p(NULL); + + if(_state == MEASUREMENT_READY) + { + p = _temperatures; + _state = IDLING; + } + + return p; +} + +void MeasureUnit::init() +{ + +} diff --git a/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.h b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.h new file mode 100644 index 0000000..28aa801 --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit.h @@ -0,0 +1,50 @@ +#ifndef MEASUREUNIT_H +#define MEASUREUNIT_H +#include +#include "ThermistorSettings.h" + +class MeasureUnit +{ + public: + enum ERROR {OK = 0, MALLOC_ERR = 1}; + MeasureUnit(uint16_t thermistorCount, uint64_t precResistor, ThermistorSettings thermistorSettings, LTC2439 &adc); + ~MeasureUnit(); + void init(); + void run(); + + void setGlobalTempOffset(double offset); + double getGlobalTempOffset(); + double *getROffsetMap(); + + //Async methods + enum STATE {IDLING, MEASURING, COMPUTING, MEASUREMENT_READY}; + boolean startTemperatureMeasurement(); + boolean isMeasurementReady(); + double *getAsyncTemperatures(); + //End of assync methods + + ERROR getError(){return _error;} + + protected: + private: + double computeTemperature(double beta, double resistance, double rAt25); + + double _globalOffset; //Correspond à l'offset global nécessaire afin d'avoir une température qui corresponde à la réalité + double *_temperatures; //Tableau contenant toutes les températures + double *_rOffsetMap,*_rOffsetBuffer; //Tableau qui contient les offsets individuels pour chaque thermistance + double *_resistanceMap; //Tableau qui contient les resistances associées aux thermistances (pour debug seulement) + uint16_t _thermistorCount; + uint64_t _precResistor; + ERROR _error; + + LTC2439 &_adc; + ThermistorSettings _thermistorSettings; + + //Async part + STATE _state; + uint8_t _channel, _offsetComputeIte,_offsetCounter; + double _courant[2]; + boolean _triggerLevelOff; //Attribut permettant de savoir si un étalonnage a été demandé +}; + +#endif //MEASUREUNIT_H diff --git a/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit_ESP8266_LTC2439.ino b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit_ESP8266_LTC2439.ino new file mode 100644 index 0000000..f882e14 --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/MeasureUnit_ESP8266_LTC2439.ino @@ -0,0 +1,198 @@ +/** + * Cet exemple correspond à l'application de test de la bibliothèque MeasureUnit qui + * permet de calculer la température qui est fonction de la résistance d'une matrice de thermistance + * + * Anatole SCHRAMM-HENRY + * Tim THUREL + * Version compatible avce le LTC2439 + * 10/05/2020 + */ +#include +#include +#include +#include +#include +#include "MeasureUnit.h" +#include "ThermistorSettings.h" +#include + +//#define RADIO_ENABLED +#define PUSH_BUTTON 0 + +/* + * Liste des offsets trouvés + * | -0.49 | 0.36 | -0.29 | 0.38 | 0.44 | -0.35 | -0.21 | 0.14 | + * | -0.72 | 0.07 | -0.52 | -0.01 | 2.38 | -0.65 | -0.44 | -0.11 | + * | -0.99 | -0.06 | -0.74 | 2.24 | 0.73 | -0.86 | -0.68 | 0.35 | + * | -0.53 | -0.49 | -0.27 | 1.17 | 0.07 | 0.14 | -0.02 | -0.08 | + * | -0.62 | -0.73 | 1.58 | 0.42 | -0.27 | 0.09 | -0.25 | -0.21 | + + * + */ + +//Objet de calcule de la température +LTC2439 adc(2,12); + +ThermistorSettings thermistorSettings(3380, 10000); +MeasureUnit measureUnit(16, 1000, thermistorSettings, adc); +//Objet de création des trames LoRa +PayloadFormatter payloadFormatter(1,16); + +RTC_DS1307 rtc; +DateTime payloadDate; +STS21 sts21; + +boolean data(false); +uint8_t *payload(NULL), _timeCounter(0), size(0), _channel(0); +boolean calibrer(false); +unsigned long _time(0); +double *tempArray = NULL; +/* + * Radio Part +*/ +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static u1_t NWKSKEY[16] = { 0x1F, 0x9E, 0xE2, 0x7A, 0xC8, 0xBA, 0xE8, 0xEA, 0xF5, 0xC2, 0x5E, 0x47, 0x5D, 0xE0, 0x77, 0x55 }; +static u1_t APPSKEY[16] = { 0x3B, 0x89, 0x86, 0x96, 0xBB, 0xAA, 0x38, 0x1E, 0x1F, 0xC4, 0xAD, 0x03, 0xEF, 0x3F, 0x56, 0x12 }; +static u4_t DEVADDR = 0x260113D3;//0x03FF0001 ; // <-- Change this address for every node! + +u1_t dio[3] = {15,3,LMIC_UNUSED_PIN}; +PinMap pinMap(2, LMIC_UNUSED_PIN, 0, dio); +LoRaRadio radio(pinMap); + +void downlinkHandler(u1_t length, u1_t dataBeg, u1_t *data) +{ + Serial.println("Downlink received : "); + for(uint8_t i(0); i < length; i++) + { + Serial.printf("%u -> %d\n",i,data[dataBeg + i]); + } + Serial.println(); + + //Action en fonction de l'octet de commande + switch(data[dataBeg+0]) + { + case 0x01://Mise à jour de l'heure + //Octets suivants: + //2 jour 3 mois 4 année 5 heures 6 minutes + if(length == 6) + { + Serial.printf("dd: %u, m: %u, yyyy: %d, hh: %u, mm: %u\n", data[dataBeg+1], data[dataBeg+2], data[dataBeg+3]+2000, data[dataBeg+4], data[dataBeg+5]); + } + else + Serial.println("Action réglage RTC : paramètres insuffisants"); + break; + case 0x02: + /*memcpy(screenTxt,(data+dataBeg+1), length-1); + screenTxt[length-1] = '\0'; + display.stopscroll(); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setCursor(0,15); + display.setTextSize(2); + display.print(screenTxt); + display.display(); + display.startscrollleft(0,16);*/ + break; + default: + Serial.println("Action inconnue"); + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println("Start setup"); + WiFi.mode(WIFI_OFF); + //Partie concernant l'initialisation de la radio + #ifdef RADIO_ENABLED + radio.init(); + radio.setTTNSession(0x1, DEVADDR, NWKSKEY, APPSKEY); + radio.setRadioEUChannels(); + /* + * La directive setMCUClockError() permet de laisser une fenêtre plus grande pour le slot de + * réception (Downlink). En effet ce slot doit durer 2 secondes et il peut durer moins en raison + * d'imprécisions d'horloge. + */ + //radio.setMCUClockError(50); + radio.setDownlinkHandler(&(downlinkHandler)); + #endif + _time = millis(); + + if(rtc.begin()) + Serial.println("RTC Ok!"); + else + Serial.println("RTC Fail!"); + + if(sts21.begin()) + { + Serial.println("Sensor present !"); + sts21.setResolution(STS21::RES_14); + } + else + Serial.println("Sensor missing !"); + + Serial.println("End setup"); + Serial.println("| T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12 | T13 | T14 | T15 | T16 |"); + +} + +void loop() { + //Version asynchrone : + measureUnit.startTemperatureMeasurement(); + + //On peut tester si la conversion est terminée avec : + //if(measureUnit.isMeasurementReady()) + + //measureUnit.getAsyncTemperatures() renvoie NULL si la recupération de la température n'est pas terminée + tempArray = measureUnit.getAsyncTemperatures(); + + double temp = sts21.getTemperature(); + + if(tempArray != NULL) + { + Serial.print("|"); + for(int i(0); i < 16; i++) + { + Serial.print(" ");Serial.print(tempArray[i],2);Serial.print(" |"); + } + + //On affiche la trame associée: + payloadFormatter.startSession(1); + payloadDate = rtc.now(); + size = payloadFormatter.buildPayload(&payload, &payloadDate, 22.5,tempArray); + if(size != 0) + { + //Serial.print("LoRa packet --> ");Serial.print("size : ");Serial.print(size);Serial.println(" bytes"); + for(int i(0); i < size; i++) + { + payload[i] <= 0x0F ? Serial.print("0") : Serial.print(""); Serial.print(payload[i], HEX); Serial.print(" "); + } + Serial.printf("|%u-%u-%u %u:%u ext temp : %.2f \n", payloadDate.day(),payloadDate.month(),payloadDate.year(),payloadDate.hour(),payloadDate.minute(), temp); + } + else + Serial.print("Failed to build LoRa packet"); + + payloadFormatter.endSession(); + + #ifdef RADIO_ENABLED + if(_timeCounter == 30 && size != 0) + { + _timeCounter = 0; + Serial.printf("Sending data\n"); + radio.send(1, payload, size); + } + + _timeCounter++; + delay(1000); + #endif + } + + //On effectue la calibration + #ifdef RADIO_ENABLED + radio.run(); + #endif + measureUnit.run(); +} diff --git a/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.cpp b/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.cpp new file mode 100644 index 0000000..d6f234c --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.cpp @@ -0,0 +1,21 @@ +#include "ThermistorSettings.h" + +ThermistorSettings::ThermistorSettings(uint16_t beta, uint64_t rAt25) : _beta(beta), _rAt25(rAt25) +{ + +} + +ThermistorSettings::~ThermistorSettings() +{ + +} + +uint16_t ThermistorSettings::getBeta() +{ + return _beta; +} + +uint64_t ThermistorSettings::getRat25() +{ + return _rAt25; +} diff --git a/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.h b/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.h new file mode 100644 index 0000000..a6d5879 --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/ThermistorSettings.h @@ -0,0 +1,18 @@ +#ifndef THERMISTORSETTINGS_H +#define THERMISTORSETTINGS_H +#include //Necessaire afin d'avoir les types : uintxx_t + +class ThermistorSettings +{ + public: + ThermistorSettings(uint16_t beta, uint64_t rAt25); + ~ThermistorSettings(); + uint16_t getBeta(); + uint64_t getRat25(); + protected: + private: + uint16_t _beta; + uint64_t _rAt25; +}; + +#endif //THERMISTORSETTINGS_H diff --git a/lib/MeasureUnit_ESP8266_LTC2439/keywords.txt b/lib/MeasureUnit_ESP8266_LTC2439/keywords.txt new file mode 100644 index 0000000..05f86eb --- /dev/null +++ b/lib/MeasureUnit_ESP8266_LTC2439/keywords.txt @@ -0,0 +1,34 @@ +####################################### +# Syntax Coloring Map MeasureUnit +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +ThermistorSettings KEYWORD1 +AdcSetting KEYWORD1 +MeasureUnit KEYWORD1 +OK KEYWORD1 +MALLOC_ERR KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +getBeta KEYWORD2 +getRat25 KEYWORD2 +getMeasureIteration KEYWORD2 +getDelayBetweenIteration KEYWORD2 +getQuantum KEYWORD2 +getVref KEYWORD2 +setGlobalTempOffset KEYWORD2 +getGlobalTempOffset KEYWORD2 +getROffsetMap KEYWORD2 +startTemperatureMeasurement KEYWORD2 +isMeasurementReady KEYWORD2 +getAsyncTemperatures KEYWORD2 +getError KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +ERROR LITERAL1 diff --git a/lib/MeasureUnit_TTGO/Adc.cpp b/lib/MeasureUnit_TTGO/Adc.cpp new file mode 100644 index 0000000..dc8129c --- /dev/null +++ b/lib/MeasureUnit_TTGO/Adc.cpp @@ -0,0 +1,43 @@ +#include "Adc.h" + +Adc::Adc() : _lastChannel(0), _adcSetting(0,0), _state(IDLING), _sampledValue(0), _numOfSamples(0), _elapsedTime(0) +{ + //Serial.println("Adc constructor called"); +} + +Adc::~Adc() +{ + +} + +void Adc::setAdcSetting(AdcSetting adcSetting) +{ + _adcSetting = adcSetting; +} + +AdcSetting Adc::getAdcSetting() +{ + return _adcSetting; +} + +boolean Adc::isSampleReady() +{ + return _state == RESULT_READY; +} + +double Adc::getQuantum() +{ + return _adcSetting.getQuantum(); +} + +double Adc::getSampleValue() +{ + double ret(0); + if(_state == RESULT_READY) + { + ret = _sampledValue; + _state = IDLING; + } + + return ret; +} diff --git a/lib/MeasureUnit_TTGO/Adc.h b/lib/MeasureUnit_TTGO/Adc.h new file mode 100644 index 0000000..c22f267 --- /dev/null +++ b/lib/MeasureUnit_TTGO/Adc.h @@ -0,0 +1,42 @@ +#ifndef ADC_H +#define ADC_H +#include "AdcSetting.h" + +class Adc +{ + public: + virtual ~Adc() = 0; + + virtual void begin() = 0; + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true) = 0; + virtual double sampleValue() = 0; + virtual double sampleVoltage(int16_t channel, boolean sgl = true) = 0; + virtual double sampleVoltage() = 0; + + //Async methods + enum STATE {STARTED = 0, RESULT_READY, IDLING, SAMPLING}; + virtual void startSample(int16_t channel, boolean sgl = true) = 0; + virtual void startSample() = 0; + virtual double getSampleVoltage() = 0; + + boolean isSampleReady(); + double getSampleValue(); + //End of async methods + + void setAdcSetting(AdcSetting adcSetting); + AdcSetting getAdcSetting(); + protected: + Adc(); + + int16_t _lastChannel; + AdcSetting _adcSetting; + //Async part + STATE _state; + double _sampledValue; + uint8_t _numOfSamples; + unsigned long _elapsedTime; + private: +}; + +#endif //ADC_H diff --git a/lib/MeasureUnit_TTGO/AdcSetting.cpp b/lib/MeasureUnit_TTGO/AdcSetting.cpp new file mode 100644 index 0000000..d6dc318 --- /dev/null +++ b/lib/MeasureUnit_TTGO/AdcSetting.cpp @@ -0,0 +1,19 @@ +#include "AdcSetting.h" + +AdcSetting::AdcSetting(double vref, +uint8_t adcResolution, +uint8_t measureIteration, +uint16_t delayBetweenIteration) : _vref(vref), _adcResolution(adcResolution), _measureIteration(measureIteration), _delayBetweenIteration(delayBetweenIteration), _quantum(vref/(pow(2.0,(double) adcResolution)-1)) +{ + +} + +uint8_t AdcSetting::getMeasureIteration() +{ + return _measureIteration; +} + +double AdcSetting::getQuantum() +{ + return _quantum; +} diff --git a/lib/MeasureUnit_TTGO/AdcSetting.h b/lib/MeasureUnit_TTGO/AdcSetting.h new file mode 100644 index 0000000..5d7a36c --- /dev/null +++ b/lib/MeasureUnit_TTGO/AdcSetting.h @@ -0,0 +1,25 @@ +#ifndef ADCSETTING_H +#define ADCSETTING_H +#include +#include + +class AdcSetting +{ + public: + AdcSetting(double vref, uint8_t adcResolution, uint8_t measureIteration = 5, uint16_t delayBetweenIteration = 5); + ~AdcSetting(){} + + uint8_t getMeasureIteration(); + uint16_t getDelayBetweenIteration(){ return _delayBetweenIteration;} + double getQuantum(); + double getVref(){return _vref;} + protected: + private: + double _vref; + uint8_t _adcResolution; + double _quantum; + uint8_t _measureIteration; + uint16_t _delayBetweenIteration; +}; + +#endif //ADCSETTING_H diff --git a/lib/MeasureUnit_TTGO/Ads1115.cpp b/lib/MeasureUnit_TTGO/Ads1115.cpp new file mode 100644 index 0000000..e6ba689 --- /dev/null +++ b/lib/MeasureUnit_TTGO/Ads1115.cpp @@ -0,0 +1,127 @@ +#include "Ads1115.h" +#define ADS_DEBUG + +Ads1115::Ads1115() : ads1(0x48), ads2(0x49) +{ + #ifdef ADS_DEBUG + Serial.println("Ads1115 constructor called"); + #endif + //We set the adcs up: + ads1.setGain(GAIN_ONE); + ads2.setGain(GAIN_ONE); +} + +Ads1115::~Ads1115() +{ + +} + +void Ads1115::begin() +{ + #ifdef ADS_DEBUG + Serial.println("Ads1115 : begin"); + #endif + ads1.begin(); + ads2.begin(); +} + +double Ads1115::getQuantum() +{ + return 0.125; +} + +double Ads1115::sampleValue(int16_t channel, boolean sgl) +{ + double total(0); + for(int i(0); i < getAdcSetting().getMeasureIteration(); i++) + { + delay(getAdcSetting().getDelayBetweenIteration()); + total += getReading(channel, sgl); + } + + //We divide + total /= (double)getAdcSetting().getMeasureIteration(); + + //We return + return total; +} + +double Ads1115::sampleValue() +{ + return sampleValue(-1); +} + +double Ads1115::sampleVoltage(int16_t channel, boolean sgl) +{ + return sampleValue(channel, sgl)*0.125; +} + +double Ads1115::sampleVoltage() +{ + return sampleValue()*0.125; +} + +uint16_t Ads1115::getReading(int16_t channel, boolean sgl) +{ + int16_t chan(channel == -1 ? _lastChannel : channel); + + _lastChannel = chan > 8 ? 0 : chan; + + if(chan < 4) + { + return ads1.readADC_SingleEnded(chan); + } + else if(chan < 8) + { + return ads2.readADC_SingleEnded(chan - 4); + } + else return 0; +} + + +//Async part +void Ads1115::startSample(int16_t channel, boolean sgl) +{ + switch(_state) + { + case IDLING: + _state = SAMPLING; + _elapsedTime = millis(); + _sampledValue = 0.0; + _numOfSamples = 0; + //We set the last channel attribute + if(channel != -1) + { + _lastChannel = channel > 8 ? _lastChannel : channel; + } + + break; + case SAMPLING: + //If enough time elapsed, we can sample a value again + if(millis() - _elapsedTime > getAdcSetting().getDelayBetweenIteration()) + { + _sampledValue += getReading(channel, sgl); + _elapsedTime = millis(); + _numOfSamples ++; + } + + //All samples are done: + if(_numOfSamples == getAdcSetting().getMeasureIteration()) + { + _sampledValue /= (double)_numOfSamples; + _sampledValue += channel > 3 ? 82.0 : 0.0; + _state = RESULT_READY; + } + break; + } +} + +void Ads1115::startSample() +{ + startSample(-1); +} + +double Ads1115::getSampleVoltage() +{ + return getSampleValue() * 0.125; +} diff --git a/lib/MeasureUnit_TTGO/Ads1115.h b/lib/MeasureUnit_TTGO/Ads1115.h new file mode 100644 index 0000000..b215d37 --- /dev/null +++ b/lib/MeasureUnit_TTGO/Ads1115.h @@ -0,0 +1,31 @@ +#ifndef ADS1115_H +#define ADS1115_H +#include "Adc.h" +#include +#include + +class Ads1115 : public Adc +{ + public: + Ads1115(); + ~Ads1115(); + + virtual void begin(); + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true); + virtual double sampleValue(); + virtual double sampleVoltage(int16_t channel, boolean sgl = true); + virtual double sampleVoltage(); + + //Async methods + virtual void startSample(int16_t channel, boolean sgl = true); + virtual void startSample(); + virtual double getSampleVoltage(); + //End of async methods + protected: + private: + uint16_t getReading(int16_t channel = -1, boolean sgl = true); + Adafruit_ADS1115 ads1, ads2; +}; + +#endif //ADS1115_H diff --git a/lib/MeasureUnit_TTGO/Ads1115V2.cpp b/lib/MeasureUnit_TTGO/Ads1115V2.cpp new file mode 100644 index 0000000..8e7d7db --- /dev/null +++ b/lib/MeasureUnit_TTGO/Ads1115V2.cpp @@ -0,0 +1,134 @@ +#include "Ads1115V2.h" +#define ADSV2_DEBUG + +Ads1115V2::Ads1115V2() : ads1(ADS1115_ADDRESS_ADDR_GND), ads2(ADS1115_ADDRESS_ADDR_VDD) +{ + #ifdef ADSV2_DEBUG + Serial.println("Ads1115 constructor called"); + #endif +} + +Ads1115V2::~Ads1115V2() +{ + +} + +void Ads1115V2::begin() +{ + #ifdef ADSV2_DEBUG + Serial.println("Ads1115V2 : begin"); + #endif + Wire.begin(); + //We set the adcs up: + ads1.initialize(); + ads2.initialize(); + ads1.setMode(ADS1115_MODE_SINGLESHOT); + ads2.setMode(ADS1115_MODE_SINGLESHOT); + ads1.setRate(ADS1115_RATE_250); + ads2.setRate(ADS1115_RATE_250); + ads1.setGain(ADS1115_PGA_4P096); + ads2.setGain(ADS1115_PGA_4P096); +} + +double Ads1115V2::getQuantum() +{ + return 0.125; +} + +double Ads1115V2::sampleValue(int16_t channel, boolean sgl) +{ + double total(0); + for(int i(0); i < getAdcSetting().getMeasureIteration(); i++) + { + delay(getAdcSetting().getDelayBetweenIteration()); + total += getReading(channel, sgl); + } + + //We divide + total /= (double)getAdcSetting().getMeasureIteration(); + + //We return + return total; +} + +double Ads1115V2::sampleValue() +{ + return sampleValue(-1); +} + +double Ads1115V2::sampleVoltage(int16_t channel, boolean sgl) +{ + return sampleValue(channel, sgl)*0.125; +} + +double Ads1115V2::sampleVoltage() +{ + return sampleValue()*0.125; +} + +uint16_t Ads1115V2::getReading(int16_t channel, boolean sgl) +{ + int16_t chan(channel == -1 ? _lastChannel : channel); + + _lastChannel = chan > 8 ? 0 : chan; + + if(chan < 4) + { + ads1.setMultiplexer(chan+4); + return ads1.getConversion(true); + } + else if(chan < 8) + { + ads2.setMultiplexer(chan); + return ads2.getConversion(true); + } + else return 0; +} + + +//Async part +void Ads1115V2::startSample(int16_t channel, boolean sgl) +{ + switch(_state) + { + case IDLING: + _state = SAMPLING; + _elapsedTime = millis(); + _sampledValue = 0.0; + _numOfSamples = 0; + //We set the last channel attribute + if(channel != -1) + { + _lastChannel = channel > 8 ? _lastChannel : channel; + } + + break; + case SAMPLING: + //If enough time elapsed, we can sample a value again + if(millis() - _elapsedTime > getAdcSetting().getDelayBetweenIteration()) + { + _sampledValue += getReading(channel, sgl); + _elapsedTime = millis(); + _numOfSamples ++; + } + + //All samples are done: + if(_numOfSamples == getAdcSetting().getMeasureIteration()) + { + _sampledValue /= (double)_numOfSamples; + _sampledValue += channel > 3 ? 82.0 : 0.0; + _state = RESULT_READY; + } + break; + } +} + +void Ads1115V2::startSample() +{ + startSample(-1); +} + +double Ads1115V2::getSampleVoltage() +{ + return getSampleValue() * 0.125; +} diff --git a/lib/MeasureUnit_TTGO/Ads1115V2.h b/lib/MeasureUnit_TTGO/Ads1115V2.h new file mode 100644 index 0000000..a80ea9b --- /dev/null +++ b/lib/MeasureUnit_TTGO/Ads1115V2.h @@ -0,0 +1,31 @@ +#ifndef ADS1115V2_H +#define ADS1115V2_H +#include "Adc.h" +#include +#include + +class Ads1115V2 : public Adc +{ + public: + Ads1115V2(); + ~Ads1115V2(); + + virtual void begin(); + virtual double getQuantum(); + virtual double sampleValue(int16_t channel, boolean sgl = true); + virtual double sampleValue(); + virtual double sampleVoltage(int16_t channel, boolean sgl = true); + virtual double sampleVoltage(); + + //Async methods + virtual void startSample(int16_t channel, boolean sgl = true); + virtual void startSample(); + virtual double getSampleVoltage(); + //End of async methods + protected: + private: + uint16_t getReading(int16_t channel = -1, boolean sgl = true); + ADS1115 ads1, ads2; +}; + +#endif //ADS1115V2_H diff --git a/lib/MeasureUnit_TTGO/MeasureUnit.cpp b/lib/MeasureUnit_TTGO/MeasureUnit.cpp new file mode 100644 index 0000000..c5e9241 --- /dev/null +++ b/lib/MeasureUnit_TTGO/MeasureUnit.cpp @@ -0,0 +1,375 @@ +#include "MeasureUnit.h" +//#define DEBUG + +MeasureUnit::MeasureUnit(uint8_t *analogInput, +uint16_t thermistorCount, +uint64_t precResistor, +ThermistorSetting thermistorSetting, +Adc &adc) : _analogInput(analogInput), +_thermistorCount(thermistorCount), +_precResistor(precResistor), +_thermistorSetting(thermistorSetting), +_adc(adc), +_globalOffset(0), +_error(OK), +_state(IDLING), +_channel(0), +_offsetComputeIte(7), +_offsetCounter(7), +_courant(0.0), +_tension(0.0), +_triggerLevelOff(false) +{ + //Allocation dynamique des différent tableaux + _temperatures = (double*) calloc(_thermistorCount, sizeof(double)); + _rOffsetMap = (double*) calloc(_thermistorCount, sizeof(double)); + _resistanceMap = (double*) malloc(_thermistorCount * sizeof(double)); + _rOffsetBuffer = (double*) malloc(_thermistorCount * sizeof(double)); + + if(_temperatures == NULL || _rOffsetMap == NULL || _resistanceMap == NULL || _rOffsetBuffer == NULL) + { + _error = MALLOC_ERR; + _temperatures != NULL ? free(_temperatures):(void)_temperatures; + _rOffsetMap != NULL ? free(_rOffsetMap):(void)_rOffsetMap; + _resistanceMap != NULL ? free(_resistanceMap):(void)_resistanceMap; + _rOffsetBuffer != NULL ? free(_rOffsetBuffer):(void)_rOffsetBuffer; + + _temperatures = NULL; + _rOffsetMap = NULL; + _resistanceMap = NULL; + _rOffsetBuffer = NULL; + } + + _adc.begin(); +} + +MeasureUnit::~MeasureUnit() +{ + if(_error != MALLOC_ERR) + { + free(_temperatures); + free(_rOffsetMap); + free(_resistanceMap); + free(_rOffsetBuffer); + } +} + +void MeasureUnit::run() +{ + switch(_state) + { + case MEASURING: + _adc.startSample(_channel); + + if(_channel == 0) //Calcule du courant + { + if(_adc.isSampleReady()) + { + _tension = _adc.getSampleVoltage(); + _courant = _tension / (double) _precResistor; + #ifdef DEBUG + Serial.print("Tension prec : ");Serial.println(_tension); + #endif + _channel++; + } + } + else //Calcule des niveaux de tensions + { + if(_adc.isSampleReady()) + { + _resistanceMap[_channel-1] = _adc.getSampleVoltage(); + #ifdef DEBUG + Serial.print("Tension thermistances : ");Serial.println(_resistanceMap[_channel-1]); + #endif + _channel++; + } + } + + //Fin de la partie d'acquisition + if(_channel == _thermistorCount) + { + _state = COMPUTING; + _resistanceMap[_channel-1] = _adc.getAdcSetting().getVref(); + } + break; + case COMPUTING : + //Ici nous calculons les temperatures + for(int i(_thermistorCount-1); i > 0; i--) + { + //Calcule de delta : + _resistanceMap[i] -= _resistanceMap[i-1]; + #ifdef DEBUG + Serial.printf("Debug voltage delta : %u -> ",i);Serial.println(_resistanceMap[i]); + #endif + } + + //Ne pas oublier de déduire la chute de tension de la resistance de precision pour la première thermistance + _resistanceMap[0] -= _tension; + #ifdef DEBUG + Serial.printf("Debug voltage delta : 0 -> ");Serial.println(_resistanceMap[0]); + #endif + + for(int i(0); i < _thermistorCount; i++) + { + //3) Nous en déduisons la résistance + //Serial.print("Resistance ");Serial.print(i);Serial.print(" ");Serial.println(_resistanceMap[i]); + _resistanceMap[i] /= _courant; + //4) Nous en déduisons la temperature + _temperatures[i] = computeTemperature(_thermistorSetting.getBeta(), _resistanceMap[i], _thermistorSetting.getRat25()); + + //On effectue un étalonnage + if(_triggerLevelOff) + { + double averageTemp(0); + //We reset the offset + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = 0; + } + + for(int i(0); i < _thermistorCount; i++) + { + averageTemp += _temperatures[i]; + } + + averageTemp /= (double)_thermistorCount; + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _temperatures[i]; + } + _triggerLevelOff = false; + } + + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + #ifdef DEBUG_TEMP + Serial.print("Temperature ");Serial.print(i);Serial.print(" : ");Serial.println(_temperatures[i]); + #endif + } + _state = MEASUREMENT_READY; + break; + } + + if(_offsetCounter < _offsetComputeIte && isMeasurementReady()) + { + //We reset the offset array + if(_offsetCounter == 0) + { + Serial.printf("Initiating average \n"); + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] = 0; + _rOffsetMap[i] = 0; + } + } + else if(_offsetCounter == _offsetComputeIte-1) + { + double averageTemp(0); + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] += _temperatures[i]; + } + //We compute the average for each thermistor: + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] /= _offsetCounter; + averageTemp += _rOffsetBuffer[i]; + } + + averageTemp /= _thermistorCount; + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _rOffsetBuffer[i]; + } + + Serial.println("Offset done"); + Serial.print("|"); + for(int i(0); i < _thermistorCount; i++) + { + if(i != 7) + { + Serial.print(" ");Serial.print(_rOffsetMap[i],2);Serial.print(" |"); + } + else + { + Serial.print(" ");Serial.print(_rOffsetMap[i],2);Serial.print(" |"); + } + } + Serial.println(""); + } + else + { + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetBuffer[i] += _temperatures[i]; + } + } + + _offsetCounter++; + } +} + +void MeasureUnit::setOffsetIteration(uint8_t iteration) +{ + _offsetComputeIte = iteration; + _offsetCounter = iteration; +} + +void MeasureUnit::startOffsetComputing() +{ + _offsetCounter = 0; +} + +/** + * Methode permettant d'effectuer les mesures de température et de les récupérer + */ +double *MeasureUnit::getTemperatures() +{ + double courant(0), rPrecTension(0); + //1) Nous calculons le courant présent dans la branche grace à la résistance de précision + #ifdef DEBUG + Serial.println("-------------"); + #endif + rPrecTension = _adc.sampleVoltage(0); + #ifdef DEBUG + Serial.println("-------------"); + Serial.print("R prec voltage mV : ");Serial.println(rPrecTension,6); + #endif + + courant = rPrecTension / (double) _precResistor; + #ifdef DEBUG + Serial.print("R prec current mA : ");Serial.println(courant,6); + #endif + + + //2) Nous calculons le delta de tensions pour chaque thermistances + for(int i(1); i < _thermistorCount; i++) + { + _resistanceMap[i-1] = _adc.sampleVoltage(_analogInput[i]); + #ifdef DEBUG + Serial.print("Voltage steps ");Serial.print(i-1);Serial.print(" : ");Serial.println(_resistanceMap[i-1]); + #endif + } + _resistanceMap[7] = _adc.getAdcSetting().getVref(); + #ifdef DEBUG + Serial.print("Voltage steps 7 : ");Serial.println(_resistanceMap[7]); + #endif + + for(int i(_thermistorCount-1); i > 0; i--) + { + //Calcule de delta : + _resistanceMap[i] -= _resistanceMap[i-1]; + #ifdef DEBUG + Serial.print("Debug voltage delta : ");Serial.println(_resistanceMap[i]); + #endif + } + + //Ne pas oublier de déduire la chute de tension de la resistance de precision pour la première thermistance + _resistanceMap[0] -= rPrecTension; + Serial.printf("Debug voltage delta : 0 -> ");Serial.println(_resistanceMap[0]); + + for(int i(0); i < _thermistorCount; i++) + { + //3) Nous en déduisons la résistance + //Serial.print("Resistance ");Serial.print(i);Serial.print(" ");Serial.println(_resistanceMap[i]); + _resistanceMap[i] /= courant; + //4) Nous en déduisons la temperature + _temperatures[i] = computeTemperature(_thermistorSetting.getBeta(), _resistanceMap[i], _thermistorSetting.getRat25()); + _temperatures[i] += _rOffsetMap[i] + _globalOffset; + #ifdef DEBUG_TEMP + Serial.print("Temperature ");Serial.print(i);Serial.print(" : ");Serial.println(_temperatures[i]); + #endif + } + + return _temperatures; +} + +double MeasureUnit::computeTemperature(double beta, double resistance, double rAt25) +{ + return (((25.0+273.15) * beta) / (beta + (25.0+273.15)*log(resistance / rAt25))) - 273.15; +} + +void MeasureUnit::setGlobalTempOffset(double offset) +{ + _globalOffset = offset; +} + +double MeasureUnit::getGlobalTempOffset() +{ + return _globalOffset; +} + +/** + * Cette méthode permet de calibrer toutes les temperatures en faisans la moyenne et appliquant un offset individuel + */ +void MeasureUnit::levelTemperaturesOff() +{ + double averageTemp(0); + //We reset the offset + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = 0; + } + + getTemperatures(); + + for(int i(0); i < _thermistorCount; i++) + { + averageTemp += _temperatures[i]; + } + + averageTemp /= _thermistorCount; + + for(int i(0); i < _thermistorCount; i++) + { + _rOffsetMap[i] = averageTemp - _temperatures[i]; + } +} + +double *MeasureUnit::getROffsetMap() +{ + return _rOffsetMap; +} + +boolean MeasureUnit::startTemperatureMeasurement() +{ + + if(_state == IDLING) + { + _state = MEASURING; + _channel = 0; + return true; + } + + return false; +} + +void MeasureUnit::levelAsyncTemperaturesOff() +{ + _triggerLevelOff = true; +} + +boolean MeasureUnit::isMeasurementReady() +{ + return _state == MEASUREMENT_READY; +} + +double *MeasureUnit::getAsyncTemperatures() +{ + double *p(NULL); + + if(_state == MEASUREMENT_READY) + { + p = _temperatures; + _state = IDLING; + } + + return p; +} + +void MeasureUnit::init() +{ + Serial.println(computeTemperature(3380, 18000, 10000)); + Serial.println(computeTemperature(3380, 11700, 10000)); +} diff --git a/lib/MeasureUnit_TTGO/MeasureUnit.h b/lib/MeasureUnit_TTGO/MeasureUnit.h new file mode 100644 index 0000000..6c61bba --- /dev/null +++ b/lib/MeasureUnit_TTGO/MeasureUnit.h @@ -0,0 +1,57 @@ +#ifndef MEASUREUNIT_H +#define MEASUREUNIT_H +#include "Ads1115.h" +#include "ThermistorSetting.h" + +//#define DEBUG_TEMP + +class MeasureUnit +{ + public: + enum ERROR {OK = 0, MALLOC_ERR = 1}; + MeasureUnit(uint8_t *analogInput, uint16_t thermistorCount, uint64_t precResistor, ThermistorSetting thermistorSetting, Adc &adc); + ~MeasureUnit(); + void init(); + void run(); + void startOffsetComputing(); + void setOffsetIteration(uint8_t iteration); + void setGlobalTempOffset(double offset); + void levelTemperaturesOff(); + double getGlobalTempOffset(); + double *getTemperatures(); + double *getROffsetMap(); + + //Async methods + enum STATE {IDLING, MEASURING, COMPUTING, MEASUREMENT_READY}; + boolean startTemperatureMeasurement(); + boolean isMeasurementReady(); + double *getAsyncTemperatures(); + void levelAsyncTemperaturesOff(); + //End of assync methods + + ERROR getError(){return _error;} + + protected: + private: + double computeTemperature(double beta, double resistance, double rAt25); + + double _globalOffset; //Correspond à l'offset global nécessaire afin d'avoir une température qui corresponde à la réalité + double *_temperatures; //Tableau contenant toutes les températures + double *_rOffsetMap,*_rOffsetBuffer; //Tableau qui contient les offsets individuels pour chaque thermistance + double *_resistanceMap; //Tableau qui contient les resistances associées aux thermistances (pour debug seulement) + uint8_t *_analogInput; //Pointeur qui garde l'adresse du tableau contenant le nom des entrées analogiques + uint16_t _thermistorCount; + uint64_t _precResistor; + ERROR _error; + + Adc &_adc; + ThermistorSetting _thermistorSetting; + + //Async part + STATE _state; + uint8_t _channel, _offsetComputeIte,_offsetCounter; + double _courant, _tension; + boolean _triggerLevelOff; //Attribut permettant de savoir si un étalonnage a été demandé +}; + +#endif //MEASUREUNIT_H diff --git a/lib/MeasureUnit_TTGO/MeasureUnit_TTGO.ino b/lib/MeasureUnit_TTGO/MeasureUnit_TTGO.ino new file mode 100644 index 0000000..9dcdff1 --- /dev/null +++ b/lib/MeasureUnit_TTGO/MeasureUnit_TTGO.ino @@ -0,0 +1,221 @@ +/** + * Cet exemple correspond à l'application de test de la bibliothèque MeasureUnit qui + * permet de calculer la température qui est fonction de la résistance d'une matrice de thermistance + * + * Anatole SCHRAMM-HENRY + * 17/12/2019 + */ +#include +#include +#include +#include +#include +#include "MeasureUnit.h" +#include "PayloadFormatter.h" +#include "Ads1115.h" +#include "LoRaRadio.h" + +#define RADIO_ENABLED +#define PUSH_BUTTON 0 + +uint8_t analogInput[] = {0,1,2,3,4,5,6,7}; +double *tempArray = NULL; +char screenTxt[30] = "LES ALEAS DU DIRECT"; + +/* + * Liste des offsets trouvés + * | -0.49 | 0.36 | -0.29 | 0.38 | 0.44 | -0.35 | -0.21 | 0.14 | + * | -0.72 | 0.07 | -0.52 | -0.01 | 2.38 | -0.65 | -0.44 | -0.11 | + * | -0.99 | -0.06 | -0.74 | 2.24 | 0.73 | -0.86 | -0.68 | 0.35 | + * | -0.53 | -0.49 | -0.27 | 1.17 | 0.07 | 0.14 | -0.02 | -0.08 | + * | -0.62 | -0.73 | 1.58 | 0.42 | -0.27 | 0.09 | -0.25 | -0.21 | + + * + */ + +//Objet de calcule de la temperature +ThermistorSetting thermistorSetting(3380, 10000); +//ThermistorSetting thermistorSetting(3650, 470); +//AdcSetting adcSetting(3300.0, 12, 310, 3); +AdcSetting adcSetting(3310, 15, 6, 10); +Ads1115 adc; +MeasureUnit measureUnit(analogInput, 8, 990, thermistorSetting, adc); +//MeasureUnit measureUnit(analogInput, 8, 99, thermistorSetting, adc); +//Objet de création des trames LoRa +PayloadFormatter payloadFormatter(2,4); + +RTC_DS3231 rtc; +DateTime payloadDate; +TwoWire sc(1); +Adafruit_SSD1306 display(128,64,&sc, 16); + +boolean data(false); +uint8_t *payload(NULL), _timeCounter(0), size(0), _channel(0); +boolean calibrer(false); +unsigned long _time(0); +/* + * Radio Part +*/ +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static u1_t NWKSKEY[16] = { 0x1F, 0x9E, 0xE2, 0x7A, 0xC8, 0xBA, 0xE8, 0xEA, 0xF5, 0xC2, 0x5E, 0x47, 0x5D, 0xE0, 0x77, 0x55 }; +static u1_t APPSKEY[16] = { 0x3B, 0x89, 0x86, 0x96, 0xBB, 0xAA, 0x38, 0x1E, 0x1F, 0xC4, 0xAD, 0x03, 0xEF, 0x3F, 0x56, 0x12 }; +static u4_t DEVADDR = 0x260113D3;//0x03FF0001 ; // <-- Change this address for every node! + +u1_t dio[3] = {26,33,32}; +PinMap pinMap(18, LMIC_UNUSED_PIN, 14, dio); +LoRaRadio radio(pinMap); + +void downlinkHandler(u1_t length, u1_t dataBeg, u1_t *data) +{ + Serial.println("Downlink received : "); + for(uint8_t i(0); i < length; i++) + { + Serial.printf("%u -> %d\n",i,data[dataBeg + i]); + } + Serial.println(); + + //Action en fonction de l'octet de commande + switch(data[dataBeg+0]) + { + case 0x01://Mise à jour de l'heure + //Octets suivants: + //2 jour 3 mois 4 année 5 heures 6 minutes + if(length == 6) + { + Serial.printf("dd: %u, m: %u, yyyy: %d, hh: %u, mm: %u\n", data[dataBeg+1], data[dataBeg+2], data[dataBeg+3]+2000, data[dataBeg+4], data[dataBeg+5]); + } + else + Serial.println("Action réglage RTC : paramètres insuffisants"); + break; + case 0x02: + memcpy(screenTxt,(data+dataBeg+1), length-1); + screenTxt[length-1] = '\0'; + display.stopscroll(); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setCursor(0,15); + display.setTextSize(2); + display.print(screenTxt); + display.display(); + display.startscrollleft(0,16); + break; + default: + Serial.println("Action inconnue"); + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println("Start setup"); + WiFi.mode(WIFI_OFF); + pinMode(PUSH_BUTTON, INPUT); + //Partie concernant l'initialisation de la radio + #ifdef RADIO_ENABLED + radio.init(); + radio.setTTNSession(0x1, DEVADDR, NWKSKEY, APPSKEY); + radio.setRadioEUChannels(); + /* + * La directive setMCUClockError() permet de laisser une fenêtre plus grande pour le slot de + * réception (Downlink). En effet ce slot doit durer 2 secondes et il peut durer moins en raison + * d'imprécisions d'horloge. + */ + radio.setMCUClockError(50); + radio.setDownlinkHandler(&(downlinkHandler)); + #endif + //Adc init + adc.setAdcSetting(adcSetting); + //measureUnit.setGlobalTempOffset(-17); + _time = millis(); + + if(rtc.begin())Serial.println("RTC Ok!"); + else Serial.println("RTC Fail!"); + sc.begin(4, 15); + if(display.begin(SSD1306_SWITCHCAPVCC,0x3C)) + { + Serial.println("SCREEN Ok!"); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setCursor(0,15); + display.setTextSize(2); + display.print(screenTxt); + display.display(); + } + else Serial.println("SCREEN Fail!"); + + display.startscrollleft(0,16); + Serial.println("End setup"); + measureUnit.init(); + Serial.println("| T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 |"); + +} + +void loop() { + //Version asynchrone : + measureUnit.startTemperatureMeasurement(); + + //On peut tester si la conversion est terminée avec : + //if(measureUnit.isMeasurementReady()) + + //measureUnit.getAsyncTemperatures() renvoie NULL si la recupération de la température n'est pas terminée + tempArray = measureUnit.getAsyncTemperatures(); + + if(tempArray != NULL) + { + Serial.print("|"); + for(int i(0); i < 8; i++) + { + if(i != 7) + { + Serial.print(" ");Serial.print(tempArray[i],2);Serial.print(" |"); + } + else + { + Serial.print(" ");Serial.print(tempArray[i],2);Serial.print(" |"); + } + } + + //On affiche la trame associée: + payloadFormatter.startSession(1); + payloadDate = rtc.now(); + size = payloadFormatter.buildPayload(&payload, &payloadDate,tempArray); + if(size != 0) + { + //Serial.print("LoRa packet --> ");Serial.print("size : ");Serial.print(size);Serial.println(" bytes"); + for(int i(0); i < size; i++) + { + payload[i] <= 0x0F ? Serial.print("0") : Serial.print(""); Serial.print(payload[i], HEX); Serial.print(" "); + } + Serial.printf("|%u-%u-%u %u:%u \n", payloadDate.day(),payloadDate.month(),payloadDate.year(),payloadDate.hour(),payloadDate.minute()); + } + else + Serial.print("Failed to build LoRa packet"); + + payloadFormatter.endSession(); + + #ifdef RADIO_ENABLED + if(_timeCounter == 30 && size != 0) + { + _timeCounter = 0; + Serial.printf("Sending data\n"); + radio.send(1, payload, size); + } + + _timeCounter++; + #endif + } + + //On effectue la calibration + if(digitalRead(PUSH_BUTTON) == 0) + { + delay(500); + measureUnit.startOffsetComputing(); + } + #ifdef RADIO_ENABLED + radio.run(); + #endif + measureUnit.run(); +} diff --git a/lib/MeasureUnit_TTGO/ThermistorSetting.cpp b/lib/MeasureUnit_TTGO/ThermistorSetting.cpp new file mode 100644 index 0000000..af5f863 --- /dev/null +++ b/lib/MeasureUnit_TTGO/ThermistorSetting.cpp @@ -0,0 +1,21 @@ +#include "ThermistorSetting.h" + +ThermistorSetting::ThermistorSetting(uint16_t beta, uint64_t rAt25) : _beta(beta), _rAt25(rAt25) +{ + +} + +ThermistorSetting::~ThermistorSetting() +{ + +} + +uint16_t ThermistorSetting::getBeta() +{ + return _beta; +} + +uint64_t ThermistorSetting::getRat25() +{ + return _rAt25; +} diff --git a/lib/MeasureUnit_TTGO/ThermistorSetting.h b/lib/MeasureUnit_TTGO/ThermistorSetting.h new file mode 100644 index 0000000..f6e205d --- /dev/null +++ b/lib/MeasureUnit_TTGO/ThermistorSetting.h @@ -0,0 +1,18 @@ +#ifndef THERMISTORSETTING_H +#define THERMISTORSETTING_H +#include //Necessaire afin d'avoir les types : uintxx_t + +class ThermistorSetting +{ + public: + ThermistorSetting(uint16_t beta, uint64_t rAt25); + ~ThermistorSetting(); + uint16_t getBeta(); + uint64_t getRat25(); + protected: + private: + uint16_t _beta; + uint64_t _rAt25; +}; + +#endif //THERMISTORSETTING_H