Добрый вечер, Помогите, пожалуйста, проверить корректность кода. Решил сделать автоматическую вентиляцию в квартире. Система должна раз в 10 минут измерять концентрацию СО2 в воздухе и включать вентилятор со скоростью пропроциональной концентрации СО2 на 10 минут до следующего замера уровня углекислого газа. Также в системе управления предусмотрен ручной режим управления (сенсорная кнопка тройка-модуль) при включении которой вентиллятор запускается на максимальную мощность. Для реализации управления скоростью вентилятора взял пример кода для диммера переменного тока от https://github.com/AlexGyver/AC_Dimmer. Пример управления сенсором СО2 на сайте Амперка. У меня остались сомнения, будет ли система нормально работать если в диммере используются аппаратные прерывания. Выкладываю схему и код. Буду очень благодарен за советы. Код (C++): /* Проект CO2 Dependent Ventillation предополагает включение принудительной виентиляции в случае превышения допустимого уровня концентрации СО2 в помещении. Для эффективного проветривания необходимо предусмотреть источник свежего воздуха - причтоную часть вентиляции, например оконный клапан. Проект выполнен с использованием примеров со следующих сайтов: https://raw.githubusercontent.com/amperka/TroykaMQ/master/examples/mq135/mq135Heater/mq135Heater.ino https://raw.githubusercontent.com/AlexGyver/AC_Dimmer/master/Sketches/dimmer_timer/dimmer_timer.ino Дополнительную информацию сайтах: https://alexgyver.ru/ и http://amperka.ru/ Описание функционала. Система имеет канальный вентилятор установленный в воздуховод помещения и вентиляционную решетку через которую происходит выведение загрязненногом воздуха. Ардуино уно/нано обеспечивает контроль концентрации в воздухе СО2 один раз в 10 минут и в зависимости от состояния включает вентилятор на масштабированную в зависимости от концентрации СО2 скорость на 10 минут до следующего измерения. Сенсорная кнопка позволяет осуществить её скрытый/малозаметный монтаж в помещении и предназначена для включения системы в принудительном режиме на максимальной скорости. Поскольку предполагается, что желание отказаться от автоматической регулировки вызвано наличием неприятных примесей в воздухе и необходимостью их скорейшего удаления. Информация о компонентах приведена в файле ReadMe */ #define dimPin 4 #define zeroPin 2 #include <CyberLib.h> // шустрая библиотека для таймера volatile int tic, Dimmer; // ----------------установки для сенсора----------------------- // библиотека для работы с датчиками MQ (Troyka-модуль) #include <TroykaMQ.h> // имя для пина, к которому подключен датчик #define PIN_MQ135 A0 // имя для пина, к которому подключен нагреватель датчика #define PIN_MQ135_HEATER 11 // создаём объект для работы с датчиком // и передаём ему номер пина выходного сигнала и нагревателя MQ135 mq135(PIN_MQ135, PIN_MQ135_HEATER); //-------------------установки для вентилятора------------------------ long previousMillis = 0; // храним время последнего включения вентиллятора long interval = 600000; // интервал между включением/выключением вентилятора int fanSpeed = 0; // создаем переменную для контроля скорости вентилятора #define VENT_POWER_PIN 8 // определяем пин для управлением питания вентилятора #define MANUAL_MANAGE_PIN 7 // определяем пин контроля ручного режима void setup() { // ----------------настройки для диммера------------------------------- Serial.begin(9600); pinMode(dimPin, OUTPUT); digitalWrite(dimPin, 0); pinMode(zeroPin, INPUT); // настраиваем порт на вход для отслеживания прохождения сигнала через ноль attachInterrupt(0, detect_up, FALLING); // настроить срабатывание прерывания interrupt0 на pin 2 на низкий уровень StartTimer1(timer_interrupt, 40); // время для одного разряда ШИМ StopTimer1(); // остановить таймер Serial.println("Start"); //--------------настройки для вентилятора----------------------------------- pinMode(VENT_POWER_PIN, OUTPUT); // устанавливаем пин управления питанием вентилятора как выход pinMode(MANUAL_MANAGE_PIN, INPUT); // устанавливаем пин управления ручным режимом как вход } void loop() { //-----------------цикл для диммера-------------------------------- // выставляем зависиомсть скорости вентиллятора от концентрации СО2 Dimmer = map(fanSpeed, 350, 1000, 250, 0); // определяем режим работы вентилятора if (MANUAL_MANAGE_PIN == LOW && mq135.readCO2() <= 350) { // выключаем вентилятор если концентрации СО2 ниже 350 ppm digitalWrite(VENT_POWER_PIN, LOW); } if (MANUAL_MANAGE_PIN == LOW && mq135.readCO2() > 350) { // устанавливаем скорость работы вентилятора в зависимости от концентрации СО2 digitalWrite(VENT_POWER_PIN, HIGH); } if (MANUAL_MANAGE_PIN !=LOW) { // устанавливаем ручной режим работы вентилятора на максимальной скорости digitalWrite(VENT_POWER_PIN, HIGH); fanSpeed = 1000; } //----------------цикл для сенсора--------------------------------- unsigned long currentMillis = millis(); //проверяем не прошел ли нужный интервал, если прошел то if(currentMillis - previousMillis > interval && MANUAL_MANAGE_PIN == LOW) { // сохраняем время последнего переключения previousMillis = currentMillis; // включаем нагреватель mq135.heaterPwrHigh(); Serial.println("Heated sensor"); // если прошёл интервал нагрева датчика // и калибровка не была совершена if (!mq135.isCalibrated() && mq135.heatingCompleted()) // выполняем калибровку датчика на чистом воздухе mq135.calibrate(); // при знании сопративления датчика на чистом воздухе // можно его указать вручную, допустим 160 // mq135.calibrate(160); // выводим сопротивление датчика в чистом воздухе (Ro) в serial-порт Serial.print("Ro = "); Serial.println(mq135.getRo()); } // если прошёл интевал нагрева датчика // и калибровка была совершена if (mq135.isCalibrated() && mq135.heatingCompleted()) { // выводим отношения текущего сопротивление датчика // к сопротивлению датчика в чистом воздухе (Rs/Ro) Serial.print("Ratio: "); Serial.print(mq135.readRatio()); // выводим значения газов в ppm Serial.print("\tCO2: "); Serial.print(mq135.readCO2()); Serial.println(" ppm"); fanSpeed = mq135.readCO2(); // устанавливаем значение концентрации СО2 в переменную контроля скорости mq135.heaterPwrLow(); // выключаем нагреватель delay(100); } } //----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ-------------------------- void timer_interrupt() { // прерывания таймера срабатывают каждые 40 мкс tic++; // счетчик if (tic > Dimmer) // если настало время включать ток digitalWrite(dimPin, 1); // врубить ток } void detect_up() { // обработка внешнего прерывания на пересекание нуля снизу tic = 0; // обнулить счетчик ResumeTimer1(); // перезапустить таймер attachInterrupt(0, detect_down, RISING); // перенастроить прерывание } void detect_down() { // обработка внешнего прерывания на пересекание нуля сверху tic = 0; // обнулить счетчик StopTimer1(); // остановить таймер digitalWrite(dimPin, 0); // вырубить ток attachInterrupt(0, detect_up, FALLING); // перенастроить прерывание } //----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ--------------------------
Сам-то понимаешь что в коде? Нарисуй алгоритм работы диммера и я тебе укажу на ошибки. Диммером можно управлять коллекторным двигателем. Асинхронным низзя - сгорит.
Работа диммера переменного тока интересно показана его создателем и доступна по ссылке . Постараюсь не исказить информацию и кратко опишу принцип работы в тексте. Основным регулирующим элементом диммера является симистор, который может пропускать ток в обоих направлениях и в тоже время выполнять роль ключа - закрывая и открывая канал для переменного тока. В сети имеется переменный ток, который меняет свою полярность с частотой 50 Гц. Следовательно в момент перехода напряжения через "ноль" можно перекрыть канал симистором на небольшое время и затем открыть, тем самым регулируя продолжительность наличия напряжения и высоту волны в одном полупериоде и в другом полупериоде при переходе через "ноль" в обратную сторону. Открывает и закрывает симмистор в данной схеме оптопара с симисторным выходом. Использование оптопар здесь и далее необходимо для гальванической развязки части схемы с высоковольтной составляющей. Теперь необходимо точно определить тот самый момент перехода через ноль и сообщить ардуино, что пора засекать время для задержки. Для этого используем транзисторную оптопару и подаем с нее сигнал на Пин 2 аппаратного прерывания ардуины. Используя этот сигнал, контроллер фиксирует, например, восходящий фронт и перенастраивает режим обработки прерывания на противоположный. Внутреннее прерывание по таймеру срабатывает каждые 40 мкс и сравнивает количество таких прерываний с переменной Dimmer - время диммирования. Если достигли необходимого количества, открываем симистор. Таким образом, как только обнаруживается переход через "ноль" снизу, таймер запускается на время диммирования и прерывание перенастраивается на переход сверху и как только таймер срабатывает открывается симистор. Далее прерывание замечает переход через "ноль" сверху таймер останавливается и прерывание перенастраивается на переход снизу и закрывается симистор. И это все повторяется 50 раз в секунду. Время диммирования определяется переменной Dimmer и должно быть от 0 до 255. Переживаю больше за часть кода которая отвечает за работу сенсора СО2 по следующим причинам: 1. Команды из раздела Setup примера амперки, я перенес в основной цикл и не уверен можно ли так делать. 2. Постоянное прерывание контроллера на регулировку диммера будет его отвлекать от работы с сенсором. Предположил, что эти прерывания занимают мкс и не внесут помех в работу датчика, если конечно там не будет конфликта в командах библиотек. 3. Добавил реле отключения нагрузки от сети, если значения сенсора СО2 в нормальных пределах и это также позволит устранить сигнал прерывания пока диммирования не нужно и отдать приоритет для основной программы. Регулируемый вентилятор - "ВЕНТС 100 -ВКО". На сайте к нему предлагают Вентс РС-1-300, который является полупроводниковым (симисторным, тиристорным) прибором для регулировки скорости вращения бытовых и промышленных вентиляторов с однофазным напряжением питания - 220 В. Предположил, что принцип диммирования в них одинаковый и можно частоту его оборотов регулировать вышеописанным способом.
Это не алгоритм, а просто описание, которое известно абсолютно всем. Алгоритмы вот: https://yandex.ru/images/search?text=блоки алгоритма&stype=image&lr=2&source=wiz Т.е. нарисовал блок-схему алгоритма и говоришь - вот это условие у меня реализовано вот в этой строчке или функции. И если хочешь составить рабочую программу, то работать надо только так. Сперва алгоритм и только потом программа по этому алгоритму.
По моим представлениям, алгоритм работы диммера может выглядеть примерно так. Как можно понять из рисунка программирование не является моей основной профессией, но я очень благодарен за наставничество, поскольку знания не имеют специализации, в отличие от людей. Заранее прошу прощения за возможные ошибки. Посмотреть вложение 13406
а здесь нет профессионалов - одни любители. Так что стесняться нечего. Хочешь чтобы помогли - надо предоставить полную картину происходящего. Рисовать в голове алгоритм работы по строчкам кода мало у кого возникает желания. А реализаций алгоритма диммера - море.
Здравствуйте, Если кому-то интересно, привожу результаты. 1. Схема работает нормально, прерывания не влияют на конечный результат (тестирую дома уже 2 месяца). Если что-то жарить на кухне, то включится, потом выключиться. Из недостатков - сложность калибровки (после некоторого времени показания ползут в сторону увеличения и приходится подправлять калибровочные значения, что он там измеряет неизвестно) и сенсорная кнопка периодически включается самостоятельно (если поставить переключатель механический, то проблем не будет). 2. Указанный выше двигатель вентилятора работает с диммером нормально, но диммирование меньше чем на половину (100 из 255) не делал. На мультиметре напряжение 150 В показывает. 3. Все помещается в небольшой распаечной коробке. 4. Функциональные элементы системы: - блок питания AC-DC 5 вольт, - ардуино нано, - датчик газа MQ135 с выходами А и D, - сенсорная кнопка, - тактовая кнопка, - датчик освещенности на базе фоторезистора и еще одного резистора на 10К, - диммер переменного тока как описан выше, - подстроечный резистор 10К, - красный светодиод, - двухцветный светодиод с общим катодом (или подправить код) или от RGB не все ноги можно взять, - распаечная коробка, - вентилятор ВЕНТС 100 -ВКО или любой, чтобы не жалко было экспериментировать на нем. Ниже описываю функционал и схему подключения (рисовать дольше чем все сделать) и прошивку. 1. Функционал системы: - Вентиляция работает в двух режимах - ручном и автоматическом (переключение между режимами тактовой кнопкой); - В каждом режиме возможно принудительное включение вытяжки на полную мощность с помощью переключателя или сенсорной кнопки (в моем случае); - Датчик освещения корректирует мощность включения вытяжки только в автоматическом режиме (ночью половина, чтобы не шумел, днем - максимум); - Датчик газов используется только в автоматическом режиме и для определения концентрации газов и включения вытяжки; - Двухцветный (можно два отдельных) светодиод демонстрирует режим работы (зел- авт; син - ручн); - Контроль напряжения на нагревательном элементе датчика газа с целью проверки его работоспособности; - Красный светодиод для индикации поломки датчика газа (если напряжение выше заданого порога). 2. Схема подключения D4 - управление диммированием; D2 - детектор нуля; А1 - аналоговый выход датчика газа MQ135; А2 - переключатель принудительного включения вентиляции (у меня сенсорная с фиксацией состояния HIGH - LOW); А3 - определение напряжения на нагревательном элементе датчика газа (необходимо подпаять провод к выводу датчика газа со стороны отрицательного провода); А4 - подключаем к фоторезистору по классической схеме; D6 - подключаем к красному светодиоду индикации ошибки работы нагревательного элемента; D12 - подключаем к зеленому аноду (у меня с общим катодом) светодиода индикации автоматического режима работы системы ; D9 - подключаем к синему аноду (у меня с общим катодом) светодиода индикации ручного режима работы системы; А5 - тактовая кнопка смены режима работы ручной-автоматический (при нажатии подаем "ноль", подтягивающий резистор не нужен, настроена внутренняя подтяжка); А0 - подключаем к подстроечному резистору для корректировки напряжения для ночного режима.
3. Прошивка Код (C++): /* Диммер переменки на Arduino. Симистор через оптопару подключен к 4 пину, детектор нуля ко 2 пину. Переменная Dimmer - величина диммирования, от 0 до 255 В этом коде на пин А0 подключен потенциометр для управления яркостью */ // -----------------БИБЛИОТЕКИ-И КОНСТАНТЫ---------------------------------------------// // шустрая библиотека для таймера диммера #include <CyberLib.h> // библиотека для работы с датчиками MQ (Troyka-модуль) #include <TroykaMQ.h> #define PERIOD_1 30000 // период проверки показаний датчика газа #define GAS_DANGER 750 // показания датчика газа, при которых необходимо включать вентилляцию #define NIGHT 150 // УРОВЕНЬ ОСВЕЩЕННОСТИ ДЛЯ НОЧНОГО РЕЖИМА #define GAS_VOLTAGE 100 // напряжение на нагревательном элементе в норме #define PERIOD_2 500 // период мигания диода ошибки #define DEBOUNCE 100 // таймаут антидребезга, миллисекунды //----------------ПИНЫ---------------------------------------------------------// // имя пина, который управляет диммированием #define dimPin 4 // имя пина, который определяет переход через ноль #define zeroPin 2 // имя для пина, к которому подключен датчик газа #define PIN_MQ135 A1 // имя пина, к которому подключена кнопка принудительного включения вентилляции #define buttonPin A2 // имя пина, который управляет вкючением реле мотора // #define powerPin 5 // имя пина, который определеяет проверяет напряжение на нагревательном элементе датчика газа #define gasCheckPin A3 // имя пина, который следит за освещенностью в комнате #define lightPin A4 // имя пина, который моргает светодиодом при выходе из строя нагревательного элемента датчика газа #define errorPin 6 // имя пина, который показывает автоматический режим работы (зеленый) #define autoSetPin 12 // имя пина, который показывает ручной режим работы (синий) #define manualSetPin 9 // имя пина, к которому подключена кнопка смены режима автоматический-ручной #define setBTN A5 // -----------------------------ПЕРЕМЕННЫЕ--------------------------------------------// // создаём объект для работы с датчиком и передаём ему номер пина MQ135 mq135(PIN_MQ135); volatile int tic, Dimmer; boolean buttonState; int ppm; unsigned long timer_1; // переменная для таймера периодичности проверки показателей датчика газа unsigned long timer_2; // переменная для таймера периодичности мигания диода поломки нагревательного элемента int lightLevel = 0; // переменная для получения уровня освещенности int gasCheckState; // переменная для определения напряжения на нагревательном элементе датчика газа boolean errorState = LOW; // переменная для управления индикатором поломки нагревательного элемента boolean btnState, btnFlag; // переменные для управления режимом (ручной/автоматический)работы unsigned long debounceTimer; boolean setRgimen = LOW; void setup() { Serial.begin(9600); // pinMode(powerPin, OUTPUT); pinMode(errorPin, OUTPUT); pinMode(dimPin, OUTPUT); pinMode (PIN_MQ135, INPUT); pinMode (gasCheckPin, INPUT); pinMode (lightPin, INPUT); pinMode (buttonPin, INPUT); pinMode(zeroPin, INPUT); // настраиваем порт на вход для отслеживания прохождения сигнала через ноль pinMode(autoSetPin, OUTPUT); pinMode(manualSetPin, OUTPUT); pinMode(setBTN, INPUT_PULLUP); digitalWrite(dimPin, 0); attachInterrupt(0, detect_up, FALLING); // настроить срабатывание прерывания interrupt0 на pin 2 на низкий уровень digitalWrite(errorPin, 0); digitalWrite(autoSetPin, 0); digitalWrite(manualSetPin, 1); StartTimer1(timer_interrupt, 40); // время для одного разряда ШИМ StopTimer1(); // остановить таймер mq135.calibrate(450); // после калибровки датчика на чистом воздухе вписать полученноезначение например, mq135.calibrate(160); Serial.println("Start"); } void loop() { buttonState = digitalRead (buttonPin); lightLevel = analogRead (lightPin); // записываем уровень освещенности gasCheckState = analogRead (gasCheckPin); // получаем напряжение на нагревательном элементе датчика газа digitalWrite(errorPin, errorState); // сигнализируем об ошибке, если обнаружена digitalWrite(autoSetPin, setRgimen); digitalWrite(manualSetPin, !setRgimen); // -------------------------РАБОТА КНОПКИ ПЕРЕКЛЮЧЕНИЯ РЕЖИМА--------------------// btnState = !digitalRead(setBTN); // читаем состояние кнопки с инверсией. 1 - нажата, 0 - нет // если нажата и была отпущена (btnFlag 0) и прошло не менее DEBOUNCE времени if (btnState && !btnFlag && (millis() - debounceTimer > DEBOUNCE)) { setRgimen = !setRgimen; // меняем режим по нажатию кнопки btnFlag = true; // запомнили что нажата включаем автоматический режим debounceTimer = millis(); // запомнили время нажатия Serial.println("press"); } if (!btnState && btnFlag) { // если отпущена и была нажата (btnFlag 1) включаем ручной режим btnFlag = false; // запомнили что отпущена debounceTimer = millis(); // запомнили время отпускания Serial.println("release"); } // --------------------------------- УПРАВЛЕНИЕ ВЕНТИЛЛЯЦИЕЙ---------------// //---------------------ЧИТАЕМ ПОКАЗАНИЯ ДАТЧИКА ГАЗА------------------------// if (millis() - timer_1 > PERIOD_1) { // условие таймера ppm = mq135.readCO2(); // выполняем проверку показаний датчика каждые PERIOD_1 миллисекунд //Serial.print("buttonState ="); // Serial.print(buttonState); // проверяем работу датчика газа // Serial.print("\t"); Serial.print("ppm="); Serial.print(ppm); // проверяем работу датчика газа Serial.print("\t"); Serial.print("light="); Serial.print(lightLevel); // проверяем работу датчика освещения Serial.print("\t"); Serial.print("dimmer="); Serial.print(Dimmer); // проверяем работу переменной диммирования Serial.print("\t"); Serial.print("gasVoltage="); Serial.println(gasCheckState); // проверяем напряжение на нагревательном элементе timer_1 = millis(); // сброс таймера } //---------------------------ВКЛЮЧАЕМ ВЕНТИЛЛЯЦИЮ САМОСТОЯТЕЛЬНО-----------------------// if (buttonState == HIGH) { // если кнопка ручного управления включена подаем напряжение на вентиллятор и устанавливаем диммер на максимум // digitalWrite (powerPin, HIGH); Dimmer = 0; } else if (buttonState == LOW && setRgimen == HIGH) { Dimmer = 255; } //-------------------------------УПРАВЛЯЕМ В АВТОМАТИЧЕСКОМ РЕЖИМЕ-------------------// if (ppm < GAS_DANGER && buttonState == LOW && setRgimen == LOW) { // выключаем если датчик показывает низкие значения Dimmer = 255; } if (ppm > GAS_DANGER && buttonState == LOW && setRgimen == LOW) { if (lightLevel < NIGHT) { Dimmer = map(analogRead(0), 0, 1023, 100, 0); // необходимо подобрать оптимальное напряжение (вместо цифр 100, 0) где 255 будет равно напряжению 0 В, а 0 буде равен напряжению 220 В } if (lightLevel >= NIGHT) { Dimmer = 0; } } // -------------------------------ПРОВЕРЯЕМ РАБОТОСПОСОБНОСТЬ ДАТЧИКА ГАЗА----------------------------// if (gasCheckState < GAS_VOLTAGE && setRgimen == LOW) { // если напряжение упало, значит нагревательный элемент перегорел if (millis() - timer_2 > PERIOD_2) { // мигаем раз в секунду если находимся в автоматическом режиме errorState = !errorState; // инвертируем состояния для мигания диодом сигнализирующим об ошибке timer_2 = millis(); // сброс таймера } } else { errorState = LOW; } } //----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ-------------------------- void timer_interrupt() { // прерывания таймера срабатывают каждые 40 мкс tic++; // счетчик if (tic > Dimmer) // если настало время включать ток digitalWrite(dimPin, 1); // врубить ток } void detect_up() { // обработка внешнего прерывания на пересекание нуля снизу tic = 0; // обнулить счетчик ResumeTimer1(); // перезапустить таймер attachInterrupt(0, detect_down, RISING); // перенастроить прерывание } void detect_down() { // обработка внешнего прерывания на пересекание нуля сверху tic = 0; // обнулить счетчик StopTimer1(); // остановить таймер digitalWrite(dimPin, 0); // вырубить ток attachInterrupt(0, detect_up, FALLING); // перенастроить прерывание } //----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ--------------------------