уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

На четвертый день работы с датчиком 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 и текущее сопротивление.

5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии