Arduino:Примеры/TemperatureControl

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Управление температурой[1]

Этот пример является расширенной версией примера «Контроллер Arduino» и показывает, как измерять и управлять температурой в бойлере, а также выводить эту температуру на график.

Код

// *** Управление температурой ***

// Это расширенная версия примера «Контроллер Arduino». Теперь PC 
// отсылает на Arduino стартовую команду, а Arduino начинает измерять 
// температуру и управлять нагревателем. Контроллер также начинает 
// отсылать на PC данные о температуре и управлении нагревателем, а PC 
// переводит их в график. Управление нагревателем осуществляется при 
// помощи ползунка и библиотеки PID – с их помощью можно задать 
// целевую температуру.

// Если у вас нет термопары или нагревающего элемента, в этот скетч 
// встроена симуляция бойлера. Чтобы воспользоваться ею, 
// отключите #define REAL_HEATER

// ПРИМЕЧАНИЕ: Если вы для загрузки библиотеки CmdMessenger 
// использовали диспетчер пакетов, убедитесь, что у вас также 
// подключены эти библиотеки: 
// * PID
// * Adafruit_MAX31855 (в режиме симуляции не обязательна)
// 
// Пакет, содержащий все эти библиотеки, можно найти тут:
https://github.com/thijse/Zipballs/blob/master/CmdMessenger/CmdMessenger.zip?raw=true



#include <utility\HeaterSim.h>
 
//#define REAL_HEATER;
#ifdef REAL_HEATER
#include <Adafruit_MAX31855.h>
#else
#include <utility\HeaterSim.h>
#endif

#include <CmdMessenger.h>  
#include <PID_v1.h>
#include <utility\\DoEvery.h>   


// привязываем экземпляр класса CmdMessenger к последовательному порту:
CmdMessenger cmdMessenger(Serial); 



const int heaterPwmInterval        = 300; // продолжительность ШИМ-цикла 
const int measureInterval          = 100;  // интервал между измерениями

DoEvery tempTimer(measureInterval);
DoEvery pidTimer(heaterPwmInterval);

// настройки PID:
double pidP                        = 1500;
double pidI                        = 25;
double pidD                        = 0;

// контакты термопары:
int thermoDO                       = 3;
int thermoCS                       = 4;
int thermoCLK                      = 5;

// контакт для реле:
const int switchPin                = 4;

bool acquireData                   = false; // флаг для запуска/остановки записи логов
bool controlHeater 	               = false; // флаг для запуска/остановки управления нагревателем

long startAcqMillis                = 0;

double CurrentTemperature          = 20;    // измеренная температура
double goalTemperature             = 20;    // целевая температура

bool heaterOn                      = false; // начальное состояние нагревателя 
double heaterSteerValue            = 0;     // начальное нормализированное значение для управления нагревателем

// инициализируем библиотеку для термопары:
#ifdef REAL_HEATER
Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);  
#else
HeaterSim heaterSim(20);   // нагреватель находится в месте, где окружающая температура составляет 20 градусов Цельсия
#endif

// инициализируем библиотеку PID:
PID pid(&CurrentTemperature, &heaterSteerValue, &goalTemperature,pidP,pidI,pidD,DIRECT);

// Это список распознаваемых команд, и эти команды могут быть 
// либо получены, либо отправлены. Чтобы получить команду, к этому 
// событию нужно привязать функцию внешнего вызова.
enum
{
  // команды:
  kWatchdog            , // команда, запрашивающая ID устройства
  kAcknowledge         , // команда, подтверждающая получение команды
  kError               , // команда, сообщающая об ошибках
  kStartLogging        , // команда, запрашивающая начать запись логов 
  kStopLogging         , // команда, запрашивающая остановить запись логов 
  kPlotDataPoint       , // команда, запрашивающая отображение на графике точек данных
  kSetGoalTemperature  , // команда, задающая целевую температуру 
  KSetStartTime        , // команда, задающая новое начальное время для записи логов 
};

// команды, отсылаемые от PC и получаемые на Arduino; в скетче Arduino 
// нужно задать функцию внешнего вызова для каждой записи из списка ниже: 
void attachCommandCallbacks()
{
  // подключаем функции внешнего вызова:
  cmdMessenger.attach(OnUnknownCommand);
  cmdMessenger.attach(kWatchdog, OnWatchdogRequest);
  cmdMessenger.attach(kStartLogging, OnStartLogging);
  cmdMessenger.attach(kStopLogging, OnStopLogging);
  cmdMessenger.attach(kSetGoalTemperature, OnSetGoalTemperature);
  cmdMessenger.attach(KSetStartTime, OnSetStartTime);
}

// ------------------  ФУНКЦИИ ВНЕШНЕГО ВЫЗОВА  ----------------------

// эта функция вызывается, когда к присланной команде не привязано никакой функции:
void OnUnknownCommand()
{
  cmdMessenger.sendCmd(kError,"Command without attached callback");  //  "Команда без функции внешнего вызова"
}

void OnWatchdogRequest()
{
  // по запросу отсылаем на компьютер эти команду и идентификатор:
  cmdMessenger.sendCmd(kWatchdog, "77FAEDD5-FAC8-46BD-875E-5E9B6D44F85C");
}

// функция внешнего вызова, сообщающая о готовности Arduino (т.е. о ее загрузке):
void OnArduinoReady()
{
  cmdMessenger.sendCmd(kAcknowledge,"Arduino ready");  //  "Arduino готова"
}

// функция для сбора данных:
void OnStartLogging()
{
  // начинаем сбор данных:
  acquireData = true;
  cmdMessenger.sendCmd(kAcknowledge,"Start Logging");  //  "Начинаем запись логов"
}

// функция для остановки сбора данных:
void OnStopLogging()
{
  acquireData    = false;
  cmdMessenger.sendCmd(kAcknowledge,"Stop Logging");  //  "Останавливаем запись логов"
}

// функция внешнего вызова, задающая целевую температуру:
void OnSetGoalTemperature()
{
  // считываем аргумент о состоянии светодиода, интерпретируем строку как float:
  float newTemperature = cmdMessenger.readBinArg<float>();
  
  // перед изменением целевой температуры убеждаемся, что аргумент корректен:
  if (cmdMessenger.isArgOk()) {
    goalTemperature = newTemperature;
    
    // включаем управление нагревателем (вначале оно отключено)
    controlHeater = true;  
  
    // отправляем на PC сообщение с подтверждением:
    cmdMessenger.sendBinCmd(kAcknowledge,goalTemperature); 
  } else {
    // в присланной целевой температуре ошибка! Значит, шлем на PC сообщение об этой ошибке:
    cmdMessenger.sendCmd(kError,"Error in received new goal temperature");  //  "В новой целевой температуре обнаружена ошибка"
  }
}

// функция внешнего вызова, задающая начальное время:
void OnSetStartTime()
{
	// считываем аргумент о состоянии светодиода, интерпретируем строку как float:
	float startTime = cmdMessenger.readBinArg<float>();
	
	// перед изменением убеждаемся, что аргумент корректен:
	if (cmdMessenger.isArgOk()) {
		unsigned long milis =  millis();
		// транслируем секунды в миллисекунды для внутренних часов:		startAcqMillis = (unsigned long)((float)startTime*1000.0f);
		if (startAcqMillis >  milis) { startAcqMillis = milis; }
		startAcqMillis = milis- startAcqMillis;
	}
}

// ------------------ ГЛАВНАЯ ЧАСТЬ СКЕТЧА ----------------------

// блок исходных операций:
void setup() 
{
  // прослушиваем последовательное соединение на предмет сообщений 
  // от PC (115200 – это, как правило, максимальная скорость для 
  // последовательной коммуникации через USB):
  Serial.begin(115200);
    
  // многие макетные платы с bluetooth (вроде HC-05/HC-06) по 
  // умолчанию работают на скорости 9600. Настройки, указанные ниже,
  // должны этому соответствовать:
  //Serial.begin(9600);    
	
  // в конце команды не пишем символ новой строки, чтобы сократить 
  // размер отправляемых данных:
  cmdMessenger.printLfCr(false);
  
  // инициализируем таймеры:
  tempTimer.reset();
  pidTimer.reset();

  // инициализируем переменные PID:
  pid.SetOutputLimits(0,heaterPwmInterval);

  // считываем текущую температуру:
  #ifdef REAL_HEATER
	CurrentTemperature= thermocouple.readCelsius();
  #else
	CurrentTemperature = heaterSim.GetTemp();
  #endif

  // подготавливаем PID-порт для записи:
  pinMode(switchPin, OUTPUT);  

  // включаем PID и выставляем на автомат:
  pid.SetMode(AUTOMATIC);

  // выставляем время замера:
  pid.SetSampleTime(measureInterval);

  // подключаем функции внешнего вызова, заданные пользователем:
  attachCommandCallbacks();

  // отсылаем на PC сообщение о загрузке Arduino:
  cmdMessenger.sendCmd(kAcknowledge,"Arduino has started!");  //  "Arduino запущена!"
}

// блок повторяющихся операций:
void loop() 
{
  // обрабатываем данные, пришедшие по последовательному соединению,
  // и выполняем функции внешнего вызова:
  cmdMessenger.feedinSerialData();
 
  // каждые 100 миллисекунд обновляем температуру:
  if(tempTimer.check()) measure();
 
  // обновляем таймер PID:
  pidTimer.check();
  
  // проверяем, управляем ли мы нагревателем: 
  if (controlHeater) {
      // вычисляем новые параметры PID: 
      pid.Compute();
      
      // управляем нагревателем при помощи широтно-импульсной модуляции:
      heaterPWM();
  }
}

// меряем температуру в бойлере: 
void measure() {
  if (acquireData) {
     // рассчитываем время:
     float seconds     = (float) (millis()-startAcqMillis) /1000.0 ;
     
     // измеряем температуру: 
     #ifdef REAL_HEATER
		CurrentTemperature= thermocouple.readCelsius(); // измеряем при помощи термопары
     #else 
		CurrentTemperature = heaterSim.GetTemp();       // измеряем температуру в симулированном бойлере
     #endif   
         
     // отправляем данные на PC:    
     cmdMessenger.sendCmdStart(kPlotDataPoint);  
     cmdMessenger.sendCmdBinArg((float)seconds);                           // время   
     cmdMessenger.sendCmdBinArg((float)CurrentTemperature);                // измеренная температура
     cmdMessenger.sendCmdBinArg((float)goalTemperature);                   // целевая температура
     cmdMessenger.sendCmdBinArg((float)((double)heaterSteerValue/(double)heaterPwmInterval)); // нормализированное значения для управления нагревателем
     cmdMessenger.sendCmdBinArg((bool)heaterOn);                        // состояние вкл/выкл во время ШИМ-цикла
     cmdMessenger.sendCmdEnd();    
  }  
} 

void SetHeaterState(bool heaterOn)
{
	// включаем нагреватель, подключенный к реле на контакте switchPin:
	digitalWrite(switchPin,heaterOn?HIGH:LOW);
}

// задаем состояние нагревателя:
void heaterPWM()
{
  // включаем/выключаем нагреватель – в зависимости от фазы ШИМ-цикла: 
  heaterOn = pidTimer.before(heaterSteerValue);
  #ifdef REAL_HEATER
	SetHeaterState(heaterOn);			  // включаем нагреватель бойлера
  #else
	heaterSim.SetHeaterState(heaterOn); // включаем нагреватель симулированного бойлера
  #endif 
  
}

См.также

Внешние ссылки