На уроке мы проходили пример, на базе которого будем знакомиться с возможностями языка С++. Приведенная ниже программа включает в себя повторение пройденного материала + использует контейнеры, которыми мы не пользовались до сих пор.
В примере используется плата Arduino Uno, датчик DS19B20 и RGB светодиод.
Задача программы:
- Измерять температуру
- Отслеживать резкое изменение температуры
- Контролировать превышение пороговых значений
- Контролировать состояние датчика температуры
- Формировать и выдавать по запросу предупреждающее сообщение и код ошибки.
Текущая версия обрабатывает одну ошибку в течении заданного периода, выдавая сообщение о наиболее приоритетной. В дальнейшем мы познакомимся с тем, как сформировать очередь ошибок для последовательной обработки каждой.
Список возможных ошибок:
enum class Errors {
FREE,
CONNECT,
DT_UP, //резкое повышение температуры
DT_DOWN, //резкое понижение температуры
MAXVAL, //превышение верхнего порога (включаем кондей)
MINVAL, //холоднее нижнего порога (включаем обогреватель)
OK_TEMPER //температура в норме
};
OK_TEMPER не совсем в теме, но пока оставим как диагностическое сообщение не являющееся ошибкой.
using errorCode_t = Errors;
using errorMsg_t = std::pair<const char *, errorCode_t>;
Диагностическая информация будет передаваться в виде структуры содержащей текстовое сообщение и код ошибки из enum Errors. Для упрощения чтения программы назначим псевдонимы для списка ошибок (errorCode_t) и самого сообщения (errorMsg_t), т.к. понять что есть errorMsg_t значительно проще, чем std::pair<const char *, errorCode_t>
Работать с датчиком температуры будет класс DSB20. Задача класса – через заданные промежутки времени измерять температуру, рассчитывать минимальное, максимальное и среднее значение. Для подавления помех (случайных, сбойных показаний датчика) используем экспоненциальное сглаживание. Коэффициент сглаживания ALFA = 0,3.
Ниже показано, как сохраняются в массиве значения температуры:
double tempC = dS.getTempC(sA); //получение данных с датчика температуры
for(double &temp: arTemp)sum += temp; //сумма всех значений массива
double mean = sum/arSize; //вычисляем среднее значение температуры
arTemp[nAr] = ALFA * tempC + (1- ALFA) * mean; //сохраняем отфильтрованное значение
В жилом помещении температура воздуха не должна резко изменяться. Наш класс будет собирать значения температуры в массив размером AR_TEMPER_SIZE с периодичностью DT_TEMPER миллисекунд. Разница между минимальным и максимальным значением температуры в массиве, после циклического наполнения, даст данные о динамике. Резкое изменение температуры создаст сообщение об ошибке, которое будет обрабатывать другой блок программы.
resMin = std::min_element(arTemp.begin(), arTemp.end());
resMax = std::max_element(arTemp.begin(), arTemp.end());
if(abs(*resMax - *resMin) > maxStep){
String error1 = "Резкое " + static_cast<String>(resMin < resMax? "повышение":"понижение") + " температуры на " + static_cast<String>(*resMax - *resMin) + " гр.";
errorMsg = {error1, resMin < resMax? Errors::DT_UP: Errors::DT_DOWN};
}
Обратите внимание на переменную errorMsg, эта переменная является членом базового класса BasePin.
class BasePin {
protected:
const char* name; //имя устройства для диагностики
errorMsg_t errorMsg;
public:
BasePin(const char* name):name(name), errorMsg({"", Errors::FREE}){}
bool diagError(){return errorMsg.second != Errors::FREE;}
errorMsg_t& getEr(){return errorMsg;}
void clearError(){errorMsg = {"", Errors::FREE};}
};
Класс BasePin хранит имя датчика (к примеру – “кухня” или “гараж”) и структуру содержащую код и описание ошибки.
Для работы класса DSB20 нужно подключить класс таймера, который отсчитывает интервалы времени между измерениями, и класс управления индикацией Vled. Класс Vled мы уже использовали в наших примерах. Каждый экземпляр класса управляет одним светодиодом, пин которого передается в конструктор класса при инициализации. Включение индикатора осуществляется вызовом функции члена класса turnOnVLed(int nF). nF – число миганий светодиода.
Ниже привожу содержание файлов программы. Стоит выделить новые классы в отдельные файлы, но они у нас пока не доделаны. Оставлю так, как есть.
#include <DallasTemperature.h>
#include <ArduinoSTL.h>
#include <vector>
#include <utility>
#include "timer.h"
#include "vLed.h"
#define PIN_DS18B20 8
#define MIN_TEMPER_VAL 15
#define MAX_TEMPER_VAL 28
#define AR_TEMPER_SIZE 10
#define MAX_TEMPER_STEP 0.8
#define DT_TEMPER 2000
const uint8_t LED_PIN_BLUE = 9;
const uint8_t LED_PIN_GREEN = 10;
const uint8_t LED_PIN_RED = 11;
const double ALFA = 0.3;
typedef std::vector <double>::iterator temrIt_t;
//using temrIt_t = std::vector <double>::iterator;
//список возможных ошибок
enum class Errors {
FREE,
CONNECT,
DT_UP,
DT_DOWN,
MAXVAL,
MINVAL,
OK_TEMPER
};
using errorCode_t = Errors;
//using errorMsg_t = std::pair<const char *, errorCode_t>;
using errorMsg_t = std::pair<String, errorCode_t>;
// Создаем объект OneWire
OneWire oneWire(PIN_DS18B20);
// Создаем объект DallasTemperature для работы с сенсорами, передавая ему ссылку на объект для работы с 1-Wire.
DallasTemperature dallasSensors(&oneWire);
// Специальный объект для хранения адреса устройства
DeviceAddress sensorAddress={0x28, 0x14, 0xE6, 0x7F, 0x6, 0x0, 0x0, 0x3C};
//********************************************************************************
class BasePin {
protected:
const char* name; //имя устройства для диагностики
errorMsg_t errorMsg;
public:
BasePin(const char* name):name(name), errorMsg({"", Errors::FREE}){}
bool diagError(){return errorMsg.second != Errors::FREE;}
errorMsg_t& getEr(){return errorMsg;}
void clearError(){errorMsg = {"", Errors::FREE};}
};
//********************************************************************************
class DSB20: public BasePin{
DallasTemperature& dS;
DeviceAddress& sA; //адрес датчика температуры
std::vector <double> arTemp;
uint8_t arSize;
uint8_t nAr;
double maxStep;
Timer* timer;
uint32_t dTime; //интервал между запосами температуры
protected:
double getTemp();
void hotCool(double);
double expTemp(double); //фильтрация значения получаемого от датчика температуры
public:
DSB20(const char* name, DallasTemperature& dS, DeviceAddress &sA);
~DSB20();
double& getMean(); //запрос текущего значения температуры
void minmaxTemp(); //контроль динамики
bool cycle();
};
//-------------------------
DSB20::DSB20(const char* name, DallasTemperature& dS, DeviceAddress &sA) : BasePin(name),dS(dS), sA(sA){
arSize = AR_TEMPER_SIZE;
nAr = 0;
maxStep = MAX_TEMPER_STEP; //максимально допустимое изменение значения температуры в 10 сек.
timer = new Timer();
dTime = DT_TEMPER; //сделать сеттер
arTemp.reserve(arSize);
arTemp.assign(arSize, 22);
}
//-------------------------
DSB20::~DSB20(){
delete timer;
}
//-------------------------
double& DSB20::getMean(){
return arTemp[nAr]; //последнее усредненное значение
}
//-------------------------
void DSB20::hotCool(double mean){
if(mean < MIN_TEMPER_VAL)errorMsg = {"Cool", Errors::MINVAL}; //контроль крайних значений
else if(mean < MAX_TEMPER_VAL) errorMsg = {"Norm", Errors::OK_TEMPER};
else errorMsg = {"Hot", Errors::MAXVAL};
}
//-------------------------
double DSB20::expTemp(double tC){ //экспоненциальное сглаживание
double sum = 0.;
for(double &temp: arTemp)sum += temp;
double mean = sum/arSize; //вычисляем среднее значение температуры
hotCool(mean); //контроль превышения порогов
return ALFA * tC + (1- ALFA) * mean;
}
//-------------------------
void DSB20::minmaxTemp(){ //контроль динамики изменения температуры
temrIt_t resMin; // std::vector <double>::iterator resMin;
temrIt_t resMax; // std::vector <double>::iterator resMax;
resMin = std::min_element(arTemp.begin(), arTemp.end());
resMax = std::max_element(arTemp.begin(), arTemp.end());
if(abs(*resMax - *resMin) > maxStep){ //если разница
String error = "Резкое " + static_cast<String>(resMin < resMax? "повышение":"понижение") + " температуры на " + static_cast<String>(*resMax - *resMin) + " гр.";
errorMsg = {error, resMin < resMax? Errors::DT_UP: Errors::DT_DOWN};
}
}
//-------------------------
double DSB20::getTemp(){
double tempC = dS.getTempC(sA);
if(tempC == -127.0){
errorMsg = {"Not connected", Errors::CONNECT};
} else {
arTemp[nAr] = expTemp(tempC); //заполнение массива
Serial.println(static_cast<String>(nAr) + " => " + arTemp[nAr]);
if(++nAr == arSize){
minmaxTemp(); //контроль динамики изменения температуры
nAr = 0;
}
}
return tempC;
}
//--------------------------
bool DSB20::cycle(){
if(!timer->getTimer()){
timer->setTimer(dTime);
getTemp();
}
return true;
}
//********************************************************************************
// Вспомогательная функция печати значения температуры для устрйоства
void printTemperature(DeviceAddress deviceAddress){
double tempC = dallasSensors.getTempC(deviceAddress);
Serial.print("Temp C: ");
Serial.println(tempC);
}
//********************************************************************************
// Вспомогательная функция для отображения адреса датчика ds18b20
void printAddress(DeviceAddress deviceAddress){
for (uint8_t i = 0; i < 8; i++)
{
if (deviceAddress[i] < 16) Serial.print("0");
Serial.print(deviceAddress[i], HEX);
}
}
//************************************* !!!
DSB20 *dsb20;
Vled vledBlue(LED_PIN_BLUE);
Vled vledGreen(LED_PIN_GREEN);
Vled vledRed(LED_PIN_RED);
//*************************************
void setup(void){
Serial.begin(9600);
dsb20 = new DSB20("MySensor", dallasSensors, sensorAddress);
}
//************************************
void loop(void){
// Запрос на измерения датчиком температуры
dallasSensors.requestTemperatures(); // Просим ds18b20 собрать данные
if(dsb20->cycle())
if(dsb20->diagError()){
if(dsb20->getEr().second != Errors::OK_TEMPER)Serial.println(dsb20->getEr().first);
switch(dsb20->getEr().second){
case Errors::CONNECT:vledRed.turnOnVLed(20);vledGreen.turnOnVLed(20);vledBlue.turnOnVLed(20);break;
case Errors::DT_UP:vledRed.turnOnVLed(8);break;
case Errors::MAXVAL:vledRed.turnOnVLed();break;
case Errors::DT_DOWN:vledBlue.turnOnVLed(8);break;
case Errors::MINVAL:vledBlue.turnOnVLed();break;
default:break;
}
dsb20->clearError();
}
vledBlue.cycleVLed();
vledGreen.cycleVLed();
vledRed.cycleVLed();
}
Далее Timer и Vled. Там комментировать нечего.
//vLed.h
#ifndef VLED_H
#define VLED_H
#include "timer.h"
class Vled { //аппаратно - виртуальный индикатор
private:
int pin; //номер пина, к которому подключен светодиод
int nFlash; //число миганий светодиода этого индикатора
unsigned long time_x; //длительность включения светодиода
protected:
int statTimer; //состояние индикатора, текущий полупериод
Timer vTimer; //внутренний таймер виртуального индикатора
public:
bool turnOnVLed(); //включение индикатора
bool turnOnVLed(int); //включение индикатора
bool cycleVLed(); //обеспечение работы индикатора
Vled(int, int nFlash = 2, unsigned long time_x = 180);
};
#endif
//vLed.cpp
#include "Arduino.h"
#include "vLed.h"
#include "timer.h"
//++++++++++++++++++++++++++++++++++++++++++++++++++++
Vled::Vled(int pin, int nFlash= 2, unsigned long time_x = 180){
pinMode(pin, OUTPUT);
this->pin = pin;
this->nFlash = nFlash; //фиксированное число миганий этого индикатора
this->time_x = time_x; //длительность свечения
this->statTimer = 0;
}
//****************************************************
bool Vled::turnOnVLed(){ //включение индикатора c фиксированным числом миганий
return turnOnVLed(this -> nFlash);
}
//****************************************************
bool Vled::turnOnVLed(int nF){//включение индикатора с заданным числом миганий
if(this->statTimer == 0){ //если индикатор свободен
this->statTimer = nF * 2; //переводим в полупериоды
this->vTimer.setTimer(this->time_x);
return true; //индикатор включен
} else return false; //индикатор занят
}
//************************************
bool Vled::cycleVLed(){ //обеспечение работы индикатора
if(this->statTimer > 0 && !vTimer.getTimer()){ //определяем состояние таймера и если выключился,
this->vTimer.setTimer(this->time_x); //запускаем заново
int st = (this->statTimer & 1) == 0; //состояние светодиода определяется младшим разрядом счетчика индикатора
digitalWrite(this->pin, st);
this->statTimer--; //уменьшаем счетчик индикатора
}
return this->statTimer > 0;
}
//timer.h
#ifndef TIMER_H
#define TIMER_H
//class Timer;
class Timer {
private:
unsigned long ulTimer;
public:
Timer(unsigned long dT);
Timer();
~Timer(){};
//***************************** установка времени задержки таймера
bool setTimer(unsigned long dT);
//***************************** запрос состояния таймера. Возвращает true, если включен.
bool getTimer();
};
#endif
//timer.cpp
#include <Arduino.h>
#include "timer.h"
Timer::Timer(unsigned long dT){ulTimer = dT;}
Timer::Timer(){ulTimer = 0;}
//***********************************************************************
bool Timer::setTimer(unsigned long dT){
bool bActive = false;
if(ulTimer == 0){ //если таймер не включен
bActive = true;
ulTimer = millis() + dT; //задаем значение - сумма текущего времени
} //и заданного времни ожидания
return bActive;
}
//***************************** запрос состояния таймера. Возвращает true, если включен.
bool Timer::getTimer(){
if(ulTimer <= millis()){ //проверяем состояние таймера
ulTimer = 0; //если время истекло, сбрасываем заданное значение, выключаем таймер.
}
return ulTimer != 0; //возвращаем логическое состояние таймера, если включен - true
}
Пишите вопросы.