Cat hungry.png
Здравствуйте! Собираем деньги на перевод материалов по электронике(https://www.allaboutcircuits.com/education/). Реквизиты указаны здесь.

Arduino:Примеры/TemperatureControl

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

Перевод: Максим Кузьмин (Cubewriter)
Перевел 2686 статей для сайта.

Контакты:

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


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

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

Код

  1. // *** Управление температурой ***
  2.  
  3. // Это расширенная версия примера «Контроллер Arduino». Теперь PC
  4. // отсылает на Arduino стартовую команду, а Arduino начинает измерять
  5. // температуру и управлять нагревателем. Контроллер также начинает
  6. // отсылать на PC данные о температуре и управлении нагревателем, а PC
  7. // переводит их в график. Управление нагревателем осуществляется при
  8. // помощи ползунка и библиотеки PID – с их помощью можно задать
  9. // целевую температуру.
  10.  
  11. // Если у вас нет термопары или нагревающего элемента, в этот скетч
  12. // встроена симуляция бойлера. Чтобы воспользоваться ею,
  13. // отключите #define REAL_HEATER
  14.  
  15. // ПРИМЕЧАНИЕ: Если вы для загрузки библиотеки CmdMessenger
  16. // использовали диспетчер пакетов, убедитесь, что у вас также
  17. // подключены эти библиотеки:
  18. // * PID
  19. // * Adafruit_MAX31855 (в режиме симуляции не обязательна)
  20. //
  21. // Пакет, содержащий все эти библиотеки, можно найти тут:
  22. https://github.com/thijse/Zipballs/blob/master/CmdMessenger/CmdMessenger.zip?raw=true
  23.  
  24.  
  25.  
  26. #include <utility\HeaterSim.h>
  27.  
  28. //#define REAL_HEATER;
  29. #ifdef REAL_HEATER
  30. #include <Adafruit_MAX31855.h>
  31. #else
  32. #include <utility\HeaterSim.h>
  33. #endif
  34.  
  35. #include <CmdMessenger.h>  
  36. #include <PID_v1.h>
  37. #include <utility\\DoEvery.h>  
  38.  
  39.  
  40. // привязываем экземпляр класса CmdMessenger к последовательному порту:
  41. CmdMessenger cmdMessenger(Serial);
  42.  
  43.  
  44.  
  45. const int heaterPwmInterval        = 300; // продолжительность ШИМ-цикла
  46. const int measureInterval          = 100;  // интервал между измерениями
  47.  
  48. DoEvery tempTimer(measureInterval);
  49. DoEvery pidTimer(heaterPwmInterval);
  50.  
  51. // настройки PID:
  52. double pidP                        = 1500;
  53. double pidI                        = 25;
  54. double pidD                        = 0;
  55.  
  56. // контакты термопары:
  57. int thermoDO                       = 3;
  58. int thermoCS                       = 4;
  59. int thermoCLK                      = 5;
  60.  
  61. // контакт для реле:
  62. const int switchPin                = 4;
  63.  
  64. bool acquireData                   = false; // флаг для запуска/остановки записи логов
  65. bool controlHeater                     = false; // флаг для запуска/остановки управления нагревателем
  66.  
  67. long startAcqMillis                = 0;
  68.  
  69. double CurrentTemperature          = 20;    // измеренная температура
  70. double goalTemperature             = 20;    // целевая температура
  71.  
  72. bool heaterOn                      = false; // начальное состояние нагревателя
  73. double heaterSteerValue            = 0;     // начальное нормализированное значение для управления нагревателем
  74.  
  75. // инициализируем библиотеку для термопары:
  76. #ifdef REAL_HEATER
  77. Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);  
  78. #else
  79. HeaterSim heaterSim(20);   // нагреватель находится в месте, где окружающая температура составляет 20 градусов Цельсия
  80. #endif
  81.  
  82. // инициализируем библиотеку PID:
  83. PID pid(&CurrentTemperature, &heaterSteerValue, &goalTemperature,pidP,pidI,pidD,DIRECT);
  84.  
  85. // Это список распознаваемых команд, и эти команды могут быть
  86. // либо получены, либо отправлены. Чтобы получить команду, к этому
  87. // событию нужно привязать функцию внешнего вызова.
  88. enum
  89. {
  90.   // команды:
  91.   kWatchdog            , // команда, запрашивающая ID устройства
  92.   kAcknowledge         , // команда, подтверждающая получение команды
  93.   kError               , // команда, сообщающая об ошибках
  94.   kStartLogging        , // команда, запрашивающая начать запись логов
  95.   kStopLogging         , // команда, запрашивающая остановить запись логов
  96.   kPlotDataPoint       , // команда, запрашивающая отображение на графике точек данных
  97.   kSetGoalTemperature  , // команда, задающая целевую температуру
  98.   KSetStartTime        , // команда, задающая новое начальное время для записи логов
  99. };
  100.  
  101. // команды, отсылаемые от PC и получаемые на Arduino; в скетче Arduino
  102. // нужно задать функцию внешнего вызова для каждой записи из списка ниже:
  103. void attachCommandCallbacks()
  104. {
  105.   // подключаем функции внешнего вызова:
  106.   cmdMessenger.attach(OnUnknownCommand);
  107.   cmdMessenger.attach(kWatchdog, OnWatchdogRequest);
  108.   cmdMessenger.attach(kStartLogging, OnStartLogging);
  109.   cmdMessenger.attach(kStopLogging, OnStopLogging);
  110.   cmdMessenger.attach(kSetGoalTemperature, OnSetGoalTemperature);
  111.   cmdMessenger.attach(KSetStartTime, OnSetStartTime);
  112. }
  113.  
  114. // ------------------  ФУНКЦИИ ВНЕШНЕГО ВЫЗОВА  ----------------------
  115.  
  116. // эта функция вызывается, когда к присланной команде не привязано никакой функции:
  117. void OnUnknownCommand()
  118. {
  119.   cmdMessenger.sendCmd(kError,"Command without attached callback");  //  "Команда без функции внешнего вызова"
  120. }
  121.  
  122. void OnWatchdogRequest()
  123. {
  124.   // по запросу отсылаем на компьютер эти команду и идентификатор:
  125.   cmdMessenger.sendCmd(kWatchdog, "77FAEDD5-FAC8-46BD-875E-5E9B6D44F85C");
  126. }
  127.  
  128. // функция внешнего вызова, сообщающая о готовности Arduino (т.е. о ее загрузке):
  129. void OnArduinoReady()
  130. {
  131.   cmdMessenger.sendCmd(kAcknowledge,"Arduino ready");  //  "Arduino готова"
  132. }
  133.  
  134. // функция для сбора данных:
  135. void OnStartLogging()
  136. {
  137.   // начинаем сбор данных:
  138.   acquireData = true;
  139.   cmdMessenger.sendCmd(kAcknowledge,"Start Logging");  //  "Начинаем запись логов"
  140. }
  141.  
  142. // функция для остановки сбора данных:
  143. void OnStopLogging()
  144. {
  145.   acquireData    = false;
  146.   cmdMessenger.sendCmd(kAcknowledge,"Stop Logging");  //  "Останавливаем запись логов"
  147. }
  148.  
  149. // функция внешнего вызова, задающая целевую температуру:
  150. void OnSetGoalTemperature()
  151. {
  152.   // считываем аргумент о состоянии светодиода, интерпретируем строку как float:
  153.   float newTemperature = cmdMessenger.readBinArg<float>();
  154.  
  155.   // перед изменением целевой температуры убеждаемся, что аргумент корректен:
  156.   if (cmdMessenger.isArgOk()) {
  157.     goalTemperature = newTemperature;
  158.    
  159.     // включаем управление нагревателем (вначале оно отключено)
  160.     controlHeater = true;  
  161.  
  162.     // отправляем на PC сообщение с подтверждением:
  163.     cmdMessenger.sendBinCmd(kAcknowledge,goalTemperature);
  164.   } else {
  165.     // в присланной целевой температуре ошибка! Значит, шлем на PC сообщение об этой ошибке:
  166.     cmdMessenger.sendCmd(kError,"Error in received new goal temperature");  //  "В новой целевой температуре обнаружена ошибка"
  167.   }
  168. }
  169.  
  170. // функция внешнего вызова, задающая начальное время:
  171. void OnSetStartTime()
  172. {
  173.         // считываем аргумент о состоянии светодиода, интерпретируем строку как float:
  174.         float startTime = cmdMessenger.readBinArg<float>();
  175.        
  176.         // перед изменением убеждаемся, что аргумент корректен:
  177.         if (cmdMessenger.isArgOk()) {
  178.                 unsigned long milis =  millis();
  179.                 // транслируем секунды в миллисекунды для внутренних часов:            startAcqMillis = (unsigned long)((float)startTime*1000.0f);
  180.                 if (startAcqMillis >  milis) { startAcqMillis = milis; }
  181.                 startAcqMillis = milis- startAcqMillis;
  182.         }
  183. }
  184.  
  185. // ------------------ ГЛАВНАЯ ЧАСТЬ СКЕТЧА ----------------------
  186.  
  187. // блок исходных операций:
  188. void setup()
  189. {
  190.   // прослушиваем последовательное соединение на предмет сообщений
  191.   // от PC (115200 – это, как правило, максимальная скорость для
  192.   // последовательной коммуникации через USB):
  193.   Serial.begin(115200);
  194.    
  195.   // многие макетные платы с bluetooth (вроде HC-05/HC-06) по
  196.   // умолчанию работают на скорости 9600. Настройки, указанные ниже,
  197.   // должны этому соответствовать:
  198.   //Serial.begin(9600);    
  199.        
  200.   // в конце команды не пишем символ новой строки, чтобы сократить
  201.   // размер отправляемых данных:
  202.   cmdMessenger.printLfCr(false);
  203.  
  204.   // инициализируем таймеры:
  205.   tempTimer.reset();
  206.   pidTimer.reset();
  207.  
  208.   // инициализируем переменные PID:
  209.   pid.SetOutputLimits(0,heaterPwmInterval);
  210.  
  211.   // считываем текущую температуру:
  212.   #ifdef REAL_HEATER
  213.         CurrentTemperature= thermocouple.readCelsius();
  214.   #else
  215.         CurrentTemperature = heaterSim.GetTemp();
  216.   #endif
  217.  
  218.   // подготавливаем PID-порт для записи:
  219.   pinMode(switchPin, OUTPUT);  
  220.  
  221.   // включаем PID и выставляем на автомат:
  222.   pid.SetMode(AUTOMATIC);
  223.  
  224.   // выставляем время замера:
  225.   pid.SetSampleTime(measureInterval);
  226.  
  227.   // подключаем функции внешнего вызова, заданные пользователем:
  228.   attachCommandCallbacks();
  229.  
  230.   // отсылаем на PC сообщение о загрузке Arduino:
  231.   cmdMessenger.sendCmd(kAcknowledge,"Arduino has started!");  //  "Arduino запущена!"
  232. }
  233.  
  234. // блок повторяющихся операций:
  235. void loop()
  236. {
  237.   // обрабатываем данные, пришедшие по последовательному соединению,
  238.   // и выполняем функции внешнего вызова:
  239.   cmdMessenger.feedinSerialData();
  240.  
  241.   // каждые 100 миллисекунд обновляем температуру:
  242.   if(tempTimer.check()) measure();
  243.  
  244.   // обновляем таймер PID:
  245.   pidTimer.check();
  246.  
  247.   // проверяем, управляем ли мы нагревателем:
  248.   if (controlHeater) {
  249.       // вычисляем новые параметры PID:
  250.       pid.Compute();
  251.      
  252.       // управляем нагревателем при помощи широтно-импульсной модуляции:
  253.       heaterPWM();
  254.   }
  255. }
  256.  
  257. // меряем температуру в бойлере:
  258. void measure() {
  259.   if (acquireData) {
  260.      // рассчитываем время:
  261.      float seconds     = (float) (millis()-startAcqMillis) /1000.0 ;
  262.      
  263.      // измеряем температуру:
  264.      #ifdef REAL_HEATER
  265.                 CurrentTemperature= thermocouple.readCelsius(); // измеряем при помощи термопары
  266.      #else
  267.                 CurrentTemperature = heaterSim.GetTemp();       // измеряем температуру в симулированном бойлере
  268.      #endif  
  269.          
  270.      // отправляем данные на PC:    
  271.      cmdMessenger.sendCmdStart(kPlotDataPoint);  
  272.      cmdMessenger.sendCmdBinArg((float)seconds);                           // время  
  273.      cmdMessenger.sendCmdBinArg((float)CurrentTemperature);                // измеренная температура
  274.      cmdMessenger.sendCmdBinArg((float)goalTemperature);                   // целевая температура
  275.      cmdMessenger.sendCmdBinArg((float)((double)heaterSteerValue/(double)heaterPwmInterval)); // нормализированное значения для управления нагревателем
  276.      cmdMessenger.sendCmdBinArg((bool)heaterOn);                        // состояние вкл/выкл во время ШИМ-цикла
  277.      cmdMessenger.sendCmdEnd();    
  278.   }  
  279. }
  280.  
  281. void SetHeaterState(bool heaterOn)
  282. {
  283.         // включаем нагреватель, подключенный к реле на контакте switchPin:
  284.         digitalWrite(switchPin,heaterOn?HIGH:LOW);
  285. }
  286.  
  287. // задаем состояние нагревателя:
  288. void heaterPWM()
  289. {
  290.   // включаем/выключаем нагреватель – в зависимости от фазы ШИМ-цикла:
  291.   heaterOn = pidTimer.before(heaterSteerValue);
  292.   #ifdef REAL_HEATER
  293.         SetHeaterState(heaterOn);                         // включаем нагреватель бойлера
  294.   #else
  295.         heaterSim.SetHeaterState(heaterOn); // включаем нагреватель симулированного бойлера
  296.   #endif
  297.  
  298. }

См.также

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

  1. github.com - TemperatureControl.ino