На четвертый день работы с датчиком MQ-135 я сказал о том, что, зная значение, возвращаемое с аналогового пина Arduino, можно переложить все возможные вычисления с Arduino на Delphi. В моем случае, для этого есть несколько причин. Во-первых, калибровка датчика. Когда я впервые в блоге показывал процесс калибровки MQ-135, то можно было заметить, что после калибровки мне пришлось лезть в исходник библиотеки для Arduino, вносить в библиотеку правки и только после этого пробовал получить более менее адекватное значение концентрации CO2 в помещение. Согласитесь, что постоянно править код библиотеки — не самый верный путь разработки. Во-вторых, учитывая мои скудные знания C++, переложив вычисления на Delphi я смогу на привычном для себя языке программирования получать любые необходимые для меня значения.
Прежде, чем приступать к переносу кода библиотеки из C++ в Delphi, я решил разобраться с тем, откуда в этой библиотеке взялись значения констант типа:
#define CORA 0.00035 #define CORB 0.02718 #define CORC 1.39538 #define CORD 0.0018
Судя по этим числам, можно сказать, что это некие коэффициенты корреляции, полученные с графиков, представленных в технической документации на датчик. Вот с этих графиков:
Как оказалось в итоге, разработчик библиотеки (G.Krocker) воспользовался вычислениями, которые провел David Gironi, когда разбирался с работой MQ-135. Дэвид подошел к работе с датчиком творчески — провел оцифровку графиков из документации и вывел необходимые для работы коэффициенты, подробно расписав весь процесс в своем блоге, за что ему большое человеческое спасибо. Мне же остается переписать код библиотеки из C++ в Delphi и проверить работу на уже имеющемся «железе».
Итак, что там надо знать про модуль датчика MQ-135, чтобы начать с ним работать? Знать мы должны нагрузочное сопротивление, установленное на плате модуля (у меня оно в 1 кОм) и, после калибровки датчика на свежем воздухе, значение сопротивления Ro. Эти значения в библиотеке Arduino представлены константами:
/// The load resistance on the board #define RLOAD 1.0 /// Calibration resistance at atmospheric CO2 level #define RZERO 98.55
Мы же, для дальнейшей работы, сделаем эти значения полями класса:
type TMQ135 = class private FRLOAD: double; FRZERO: double; public end;
Так, в случае необходимости, мы сможем изменять эти значения в процессе дальнейшей работы, не влезая при этом в исходный код и не перезагружая скетч в Arduino. Остальные значения из файла MQ135.h можно так и оставить константами:
const /// Parameters for calculating ppm of CO2 from sensor resistance PARA = 116.6020682; PARB = 2.769034857; /// Parameters to model temperature and humidity dependence CORA = 0.00035; CORB = 0.02718; CORC = 1.39538; CORD = 0.0018; /// Atmospheric CO2 level for calibration purposes ATMOCO2 = 397.13;
Теперь переносим из файла MQ135.cpp все представленные в нем методы и делаем их методами класса TMQ135. Ниже я буду представлять код следующим образом: вначале код C++, ниже — код Delphi, которые делает тоже самое, но уже на стороне нашей программы. Итак, поехали:
Расчёт поправочного коэффициента
float MQ135::getCorrectionFactor(float t, float h) { return CORA * t * t - CORB * t + CORC - (h-33.)*CORD; }
function TMQ135.getCorrectionFactor(t, h: double): double; begin Result:=CORA*t*t-CORB*t+CORC-(h-33)*CORD; end;
Получение текущего сопротивления датчика
float MQ135::getResistance() { int val = analogRead(_pin); return ((1023./(float)val) * 5. - 1.)*RLOAD; }
function TMQ135.getResistance(const SensorValue: double): double; begin Result:=((1023/SensorValue)*5-1)*RLOAD; end;
SensorValue — значение, которое мы будем получать от Arduino с аналогового пина к которому подключен датчик.
Получение сопротивления датчика, скорректированного с учётом температуры и влажности
float MQ135::getCorrectedResistance(float t, float h) { return getResistance()/getCorrectionFactor(t, h); }
function TMQ135.getCorrectedResistance(SensorValue, t, h: double): double; begin Result:=getResistance(SensorValue)/getCorrectionFactor(t, h) end;
Получение концентрации CO2 в ppm
float MQ135::getPPM() { return PARA * pow((getResistance()/RZERO), -PARB); }
function TMQ135.getPPM(SensorValue: double): double; begin Result:=PARA* power((getResistance(SensorValue)/RZERO), -PARB); end;
Получение концентрации CO2 в ppm с учётом температуры и влажности
float MQ135::getCorrectedPPM(float t, float h) { return PARA * pow((getCorrectedResistance(t, h)/RZERO), -PARB); }
function TMQ135.getCorrectedPPM(SensorValue, t, h: double): double; begin Result:=PARA*power((getCorrectedResistance(SensorValue, t, h)/RZERO), -PARB); end;
Получение значения Ro для калибровки
float MQ135::getRZero() { return getResistance() * pow((ATMOCO2/PARA), (1./PARB)); }
function TMQ135.getRZero(SensorValue: double): double; begin Result:=getResistance(SensorValue)*power((ATMOCO2/PARA), (1/PARB)); end;
Получение значения Ro для калибровки с учётом температуры и влажности:
float MQ135::getCorrectedRZero(float t, float h) { return getCorrectedResistance(t, h) * pow((ATMOCO2/PARA), (1./PARB)); }
function TMQ135.getCorrectedRZero(SensorValue, t, h: double): double; begin Result:=getCorrectedResistance(SensorValue, t, h) * power((ATMOCO2/PARA), (1/PARB)) end;
Итоговое описание класса TMQ135 получилось следующее:
type TMQ135 = class private FRLOAD: double; FRZERO: double; public constructor Create(ARLoad, ARzero: double); destructor Destroy;override; //The calculated correction factor function getCorrectionFactor(t, h: double):double; //The sensor resistance in kOhm function getResistance(const SensorValue: double):double; //Get the resistance of the sensor, ie. the measurement value corrected for temp/hum function getCorrectedResistance(SensorValue, t, h: double):double; //Get the ppm of CO2 sensed (assuming only CO2 in the air) function getPPM(SensorValue: double):double; function getCorrectedPPM(SensorValue, t, h: double):double; //Get the resistance RZero of the sensor for calibration purposes function getRZero(SensorValue: double):double; function getCorrectedRZero(SensorValue, t, h: double): double; // The load resistance on the board property RLOAD: double read FRLOAD write FRLOAD; // Calibration resistance at atmospheric CO2 level property RZERO: double read FRZERO write FRZERO; end; implementation { TMQ135 } constructor TMQ135.Create(ARLoad, ARzero: double); begin inherited Create; FRLOAD:= ARLoad; FRZERO:= ARzero; end; destructor TMQ135.Destroy; begin inherited; end;
Теперь можно избавиться в скетче Arduino от библиотеки MQ135 и переложить все расчёта на библиотеку Delphi. Скетч для Arduino можно сделать, например, вот такой:
#include //подключаем библиотеку для DHT22 //определение постоянных значений #define analogPin A0 // аналоговый выход MQ135 подключен к пину A0 Arduino #define DHTPIN 8 //пин получения данных с датчика #define DHTTYPE DHT22 //Значение типа датчика //датчики DHT dhtSensor(DHTPIN, DHTTYPE); //инициализация датчика //переменные для работы float Value = 0; //значение без учёта температуры и влажности float h = 0; //температура float t = 0; //относительная влажность void setup() { Serial.begin(9600); // инициализация последовательного порта pinMode(analogPin, INPUT); // режим работы аналогового пина dhtSensor.begin(); //запуск датчика delay(1000); // даём датчикам небольшое время на разогрев } void loop() { delay(2000); // задержка перед выводом следующего значения h = dhtSensor.readHumidity(); //получение данных по влажности t = dhtSensor.readTemperature(); //получение данных по температуре if(isnan(h) || isnan(t)) return; //возврат если данные по температуре или влажности некорректно считались Value = analogRead(analogPin); Serial.print(Value); Serial.print("|"); Serial.print(t); Serial.print("|"); Serial.print(h); Serial.println("#"); }
Теперь напишем приложение Delphi для обработки значений датчиков с использованием нашего класса. Чтобы получить значение концентрации с датчика, достаточно сделать следующее:
1.Разбираем ответ с Arduino
function Tfmain.ParseString(const AStr: string; var AValue, AT, AH: double): boolean; var List: TStringList; S: string; begin //R|корректное_значение|обычное_значение|температура|влажность S:=Trim(AStr); if not EndsText('#', S) then exit(False); List:=TStringList.Create; try List.Delimiter:='|'; List.DelimitedText:=Copy(S, 1, length(S)-2); AValue:=StrToFloat(List[0], FS); AT:=StrToFloat(List[1], FS); AH:=StrToFloat(List[2], FS); Result:=True; finally FreeAndNil(List) end; end;
О том, как работать с COM-портом в Delphi — смотри здесь.
2. Определяем концентрацию CO2 с использованием класса TMQ135
MQ135.getCorrectedPPM(Value, T, H)
где Value, T, H — это значения, полученные из функции ParseString.
Аналогичным образом можно получать и все прочие значения с датчика, включая Ro и текущее сопротивление.