Проблема совмещения UTFT_Buttons и расходомера (датчик Холла)
- Войдите на сайт для отправки комментариев
Добрый вечер!
Задача: задавать на сенсорном ЖКД расход жидкости, получать на выходе системы заданный расход.
Имеем: Arduino Due, TFT Mega Shield 2.2, TFT 3.2'', расходомер (датчик Холла), пропорциональный клапан.
Проблема: Библиотека UTFT_Buttons живет в пространстве while(1) {...}. Если там находится запрос функции прерывания, то кнопки на экране срабатывают лишь 1 раз в 1 секунду (если успеть отжать кнопку в нужный момент) - не годится. Если выставить задержку в функции прерывания меньше 1 секунды - получаем рабочую ЖКД-панель, но плохой расходомер. Последний выдает 485 пульсов на 1 литр. При задержке 0,1 сек получаем разрешение расходомера 1,24 л/мин. (1*600/485=1,237, где 1 - минимальное число прерываний на датчике Холла).
На всякий случай сам код:
#include <UTFT.h> #include <UTouch.h> #include <UTFT_Buttons.h> #include <PID_v1.h> extern uint8_t BigFont[]; extern uint8_t SmallFont[]; extern uint8_t Ubuntubold[]; #define COLD_WATER_FLOW_SENSOR 8 // пин датчика расхода холодной воды #define COLD_WATER_PROPORTIONAL_VALVE DAC0 // пин пропорционального клапана холодной воды UTFT myGLCD(TFT01_32,38,39,40,41); UTouch myTouch(6,5,4,3,2); UTFT_Buttons MainScreenButtons(&myGLCD, &myTouch); volatile int counter; float Calc; double Setpoint, Input, Output; //переменные ПИД double SetpointVisual; // Видимая на экране double consKp=1, consKi=0.05, consKd=0.25; // Консервативные константы double aggKp=4*consKp, aggKi=4*consKi, aggKd=4*consKd; //Агрессивные константы PID myPID(&Input, &Output, &Setpoint, 2, 0, 0, REVERSE); //Input, Output, Setpoint, Kp, Ki, Kd, ControllerDirection) void setup() { pinMode(COLD_WATER_FLOW_SENSOR, INPUT); Input = 0; Setpoint = 0.00; // Предустановленное значение потока = 0 л/мин myPID.SetMode(AUTOMATIC); attachInterrupt(COLD_WATER_FLOW_SENSOR, rpm, FALLING); myGLCD.InitLCD(); myGLCD.clrScr(); myGLCD.setFont(Ubuntubold); myGLCD.fillScr(0, 0, 0); myGLCD.setColor(255, 255, 255); myTouch.InitTouch(); myTouch.setPrecision(PREC_MEDIUM); MainScreenButtons.setTextFont(Ubuntubold); } void loop (){ int butplus, butminus, butset, pressed_button; myGLCD.setFont(BigFont); butplus = MainScreenButtons.addButton( 150, 150, 60, 60, "+"); butminus = MainScreenButtons.addButton( 10, 150, 60, 60, "-"); butset = MainScreenButtons.addButton( 80, 150, 60, 60, "Set"); MainScreenButtons.drawButtons(); while(1) { myGLCD.setFont(Ubuntubold); myGLCD.print("Flow, l/min:", 10, 10); Input = WaterFlowMeterFunction(); //myGLCD.print(" ", 10, 50); myGLCD.printNumF(Input, 2, 10, 50); myGLCD.setFont(BigFont); myGLCD.print("Input:", 10, 90); //myGLCD.print(" ", 110, 90); myGLCD.printNumF(Input, 2, 110, 90); myGLCD.print("Setpoint:", 10, 110); //myGLCD.print(" ", 160, 110); //myGLCD.printNumF(Setpoint, 2, 160, 110); myGLCD.print("Output:", 10, 130); //myGLCD.print(" ", 130, 130); myGLCD.printNumF(Output, 2, 130, 130); if (myTouch.dataAvailable() == true) { pressed_button = MainScreenButtons.checkButtons(); if (pressed_button==butplus) { myGLCD.setColor(0, 0, 0); myGLCD.print(" ", 160, 110); SetpointVisual = SetpointVisual + 0.10; if (SetpointVisual > 6.0) SetpointVisual = 6.0; myGLCD.setColor(VGA_AQUA); myGLCD.printNumF(SetpointVisual, 2, 160, 110); myGLCD.setColor(VGA_WHITE); } if (pressed_button==butminus) { myGLCD.setColor(0, 0, 0); myGLCD.print(" ", 160, 110); SetpointVisual = SetpointVisual - 0.10; if (SetpointVisual < 0.0) SetpointVisual = 0.0; myGLCD.setColor(VGA_AQUA); myGLCD.printNumF(SetpointVisual, 2, 160, 110); myGLCD.setColor(VGA_WHITE); } if (pressed_button==butset) { myGLCD.setColor(0, 0, 0); myGLCD.print(" ", 160, 110); Setpoint = SetpointVisual; myGLCD.setColor(255, 255, 255); myGLCD.printNumF(Setpoint, 2, 160, 110); } if ( Input < 0.9*Setpoint || Input > 1.1*Setpoint ) // если входящий сигнал больше чем на 10% отличается от заданного {myPID.SetTunings(aggKp, aggKi, aggKd);} // используем агрессивные константы else {myPID.SetTunings(consKp, consKi, consKd);} // иначе - консервативные myGLCD.setFont(SmallFont); myGLCD.print("Kp:", 220, 150); myGLCD.print("Ki:", 220, 160); myGLCD.print("Kd:", 220, 170); myGLCD.printNumF(myPID.GetKp(), 3, 245, 150); myGLCD.printNumF(myPID.GetKi(), 3, 245, 160); myGLCD.printNumF(myPID.GetKd(), 3, 245, 170); myPID.Compute(); analogWrite(COLD_WATER_PROPORTIONAL_VALVE, Output); // COLD_WATER_PROPORTIONAL_VALVE - пин пропорц клапана холодной воды } } } float WaterFlowMeterFunction(){ // функция подсчета расхода counter = 0; interrupts(); delay (1000); noInterrupts(); float currentQ = ( counter * 60.00 / 485.00 ); return currentQ; } void rpm () //This is the function that the interupt calls { counter++; //This function measures the rising and falling edge of the hall effect sensors signal }
Очень хочется посоветовать не использовать библиотеку, которая "живет в пространстве while(1) {...}.", если это возможно, конечно
Да и подсчет расхода у вас странно реализован. Следует повесить его на прерывание. Ну или накрайняк от delay() избавиться.
Я бы тоже не стал использовать эту библиотеку, если бы были альтернативы. Пример ButtonTest из библиотеки UTouch живет абсолютно идентично: while (true) {...}. С радостью выслушаю варианты..
Подсчет расхода как раз и висит на прерывании. Как же еще опросить датчик Холла? И как избавиться от delay(), если нужно считать прерывания?
Я бы тоже не стал использовать эту библиотеку, если бы были альтернативы. Пример ButtonTest из библиотеки UTouch живет абсолютно идентично: while (true) {...}. С радостью выслушаю варианты..
Подсчет расхода как раз и висит на прерывании. Как же еще опросить датчик Холла? И как избавиться от delay(), если нужно считать прерывания?
Что нужно сделать?
Нужно получить: кликабельный сенсор, без задержек; достаточно точный расходомер. Оба условия должны выполняться..
Прерывания от датчика нужно считать в прерывании, где они и считаются - тут всё логично. :)
"Одну секунду" - в другом прерывании. Насколько я успел понять ардуиновскую идеологию - время для нас уже считается, и его можно в любой момент вычитать функцией millis( ).
Как по значению из millis() определить, что прошла секунда - это тут постоянно пишут, я уже раз пятнадцать читал. :)
Ну и когда она, секунда, прошла - регулируем пропорциональный клапан, и на дисплей выводим тоже.
Хотя, вообще-то, можно было не привязываться к секундным интервалам, а измерять и регулировать вообще непрерывно. Алгоритм, может быть, даже проще получится:
1. знаем сколько времени прошло с момента открывания клапана.
2. знаем, сколько за это время должно вылиться воды, а значит можем посчитать, сколько должно насчитаться импульсов с датчика.
3. сравниваем то что должно было насчитаться с тем что насчиталось реально. Ну и, понятно - по результатам сравнения регулируем клапан.
Всё, можете пользоваться. :)
Только осторожно. Счётчики - они не бесконечны, и при переполнении начнутся разные неожиданности при сравнении. Это надо предусмотреть.
Удалил функцию float WaterFlowMeterFunction(), соотв удалил задержку. Дописал такие строки возле while:
Пока все работает. Если у кого-то будут еще какие идеи - пишите, обязательно рассмотрю.
Только осторожно. Счётчики - они не бесконечны, и при переполнении начнутся разные неожиданности при сравнении. Это надо предусмотреть.
millis() - Возвращает количество миллисекунд с момента начала выполнения текущей программы на плате Arduino. Это количество сбрасывается на ноль, в следствие переполнения значения, приблизительно через 50 дней. (с)
Программа не расчитана на 50 дней непрерывной работы - вот и всё)
Всем спасибо за помощь!
Сегодня не расчитана - а завтра кому-нибудь придётся голову ломать, почему у него полтора месяца нормально регулировалось, а потом вдруг весь огород затопило. Правильный программист должен предусмотреть всё. :)
К тому же, я не только про millis писал, но и про counter.
Ну да ладно, заработало - и хорошо. Поздравляю! :)
Нужно получить: кликабельный сенсор, без задержек; достаточно точный расходомер. Оба условия должны выполняться..
Я спросил об этом: