Components and supplies
Arduino MKR 485 Shield
Arduino MKR1000
ArduiTouch MKR
Project description
Code
ArduiTouch Modbus Thermostat
arduino
1/* 2 * Application note: Thermostat with MODBUS via RS485 for ArduiTouch MKR 3 * Version 1.0 4 * Copyright (C) 2019 Hartmut Wendt www.zihatec.de 5 * 6 * (based on sources of https://github.com/angeloc/simplemodbusng) 7 * 8 * 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program. If not, see <http://www.gnu.org/licenses/>. 21*/ 22 23 24 25/*______Import Libraries_______*/ 26#include <Arduino.h> 27#include <SPI.h> 28#include "Adafruit_GFX.h" 29#include "Adafruit_ILI9341.h" 30#include <XPT2046_Touchscreen.h> 31 32#include <Fonts/FreeSans9pt7b.h> 33#include <Fonts/FreeSansBold9pt7b.h> 34#include <Fonts/FreeSansBold12pt7b.h> 35#include <Fonts/FreeSansBold24pt7b.h> 36#include <SimpleModbusSlaveMKR.h> 37#include "usergraphics.h" 38 39 40//#include <ArduinoRS485.h> 41//#include <ArduinoModbus.h> 42/*______End of Libraries_______*/ 43 44 45/*______Define pins for ArduiTouch _______*/ 46#define TFT_CS A3 47#define TFT_DC 0 48#define TFT_MOSI 8 49#define TFT_RST 22 50#define TFT_CLK 9 51#define TFT_MISO 10 52#define TFT_LED A2 53 54 55#define HAVE_TOUCHPAD 56#define TOUCH_CS A4 57#define TOUCH_IRQ 1 58 59/*_______End of definitions______*/ 60 61 62/*______Define sound for SAMD21 based boards _______*/ 63#define SND_PIN 2 // sound pin 64 65 66/*_______End of Sound definitions______*/ 67 68 69/*______Assign pressure_______*/ 70#define ILI9341_ULTRA_DARKGREY 0x632C 71#define MINPRESSURE 10 72#define MAXPRESSURE 2000 73/*_______Assigned______*/ 74 75/*____Calibrate TFT LCD_____*/ 76#define TS_MINX 370 77#define TS_MINY 470 78#define TS_MAXX 3700 79#define TS_MAXY 3600 80/*______End of Calibration______*/ 81 82 83/*____Modbus_____*/ 84#define BAUDRATE 9600 85#define DEFAULT_ID 1 86enum 87{ 88 // just add or remove registers 89 // The first register starts at address 0 90 ROOM_TEMP, // measured room temp from external sensor 91 SET_TEMP, // set-point temperature by user, 92 FAN_LEVEL, // level for ventilation (values 0 - 5) 93 BEEPER, // any value between 400 and 4000 will set the beeper with the given frequency for 100ms 94 DISP_ONOFF, // timer for display automatic off function (0 switch backlight off, >0 set timer for automatic off) 95 TOTAL_ERRORS, 96 // leave this one 97 TOTAL_REGS_SIZE 98 // total number of registers for function 3 and 16 share the same register array 99}; 100/*______End of Modbus______*/ 101 102 103/*____Program specific constants_____*/ 104#define MAX_TEMPERATURE 28 105#define MIN_TEMPERATURE 18 106enum { PM_MAIN, PM_OPTION, PM_CLEANING}; // Program modes 107enum { BOOT, COOLING, TEMP_OK, HEATING}; // Thermostat modes 108/*______End of specific constants______*/ 109 110 111Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); 112XPT2046_Touchscreen touch(TOUCH_CS, TOUCH_IRQ); 113 114//define _debug 115 int X,Y; 116 uint8_t Thermostat_mode = BOOT; 117 118 uint8_t iFan_level = 0; 119 uint8_t iRoom_temperature = 21; 120 uint8_t iSet_temperature = 20; 121 122 uint8_t PMode = PM_MAIN; // program mode 123 uint8_t Modbus_ID = DEFAULT_ID; // ID / address for modbus 124 bool Touch_pressed = false; 125 uint8_t Timer_Cleaning=0; 126 127 unsigned int holdingRegs[TOTAL_REGS_SIZE]; // function 3 and 16 register array 128 129 130 131 132void setup() { 133 #ifdef _debug 134 Serial.begin(9600); 135 #endif 136 137 // Init MODBUS registers 138 holdingRegs[ROOM_TEMP] = iRoom_temperature; 139 holdingRegs[SET_TEMP] = iSet_temperature; 140 holdingRegs[ROOM_TEMP] = iFan_level; 141 holdingRegs[BEEPER] = 0; 142 holdingRegs[DISP_ONOFF] = 3000; 143 144 #ifndef _debug 145 /* parameters(long baudrate, 146 unsigned char ID, 147 bool HalfDuplexEnable, 148 unsigned int holding registers size, 149 unsigned char low latency) 150 151 For RS485 (half duplex) set the HalfDuplexEnable to true, 152 for RS422 (full duplex) set the HalfDuplexEnable to false 153 Low latency delays makes the implementation non-standard 154 but practically it works with all major modbus master implementations. 155 */ 156 modbus_configure(BAUDRATE, Modbus_ID, true, TOTAL_REGS_SIZE, 0); 157 #endif 158 159 160 touch.begin(); 161 tft.begin(); 162 #ifdef _debug 163 Serial.print("tftx ="); Serial.print(tft.width()); Serial.print(" tfty ="); Serial.println(tft.height()); 164 #endif 165 166 draw_main_screen(); 167 pinMode(TFT_LED, OUTPUT); 168 digitalWrite(TFT_LED, LOW); // HIGH to Turn on; 169 170 171} 172 173 174TS_Point p; 175 176void loop() { 177 178 if (Touch_Event()== true) { 179 X = p.y; Y = p.x; 180 181 if (Touch_pressed == false) { 182 if (holdingRegs[DISP_ONOFF]) DetectButtons(); 183 holdingRegs[DISP_ONOFF] = 3000; // reset BL timer 184 } 185 Touch_pressed = true; 186 187 } else { 188 Touch_pressed = false; 189 } 190 191 192 193 //automatic display BL timeout 194 if (holdingRegs[DISP_ONOFF]) { 195 holdingRegs[DISP_ONOFF]--; 196 digitalWrite(TFT_LED, LOW); // Backlight off 197 } else { 198 digitalWrite(TFT_LED, HIGH); // Backlight on 199 } 200 201 // screen cleaning 202 Cleaning_processing(); 203 204 // Modbus 205 Modbus_processing(); 206 delay(10); 207 208} 209 210 211/********************************************************************//** 212 * @brief detects a touch event and converts touch data 213 * @param[in] None 214 * @return boolean (true = touch pressed, false = touch unpressed) 215 *********************************************************************/ 216bool Touch_Event() { 217 if (touch.tirqTouched()) { 218 if (touch.touched()) { 219 p = touch.getPoint(); 220 delay(1); 221 p.x = map(p.x, TS_MINX, TS_MAXX, 0, 320); 222 p.y = map(p.y, TS_MINY, TS_MAXY, 240, 0); 223 if (p.z > MINPRESSURE) return true; 224 } 225 } 226 return false; 227} 228 229 230 231 232/********************************************************************//** 233 * @brief Processing for screen cleaning function 234 * @param[in] None 235 * @return None 236 *********************************************************************/ 237void Cleaning_processing() 238{ 239 // idle timer for screen cleaning 240 if (PMode == PM_CLEANING) { 241 if ((Timer_Cleaning % 10) == 0) { 242 tft.fillRect(0,0, 100, 60, ILI9341_BLACK); 243 tft.setCursor(10, 50); 244 tft.print(Timer_Cleaning / 10); 245 246 } 247 if (Timer_Cleaning) { 248 Timer_Cleaning--; 249 } else { 250 draw_option_screen(); 251 PMode = PM_OPTION; 252 } 253 } 254} 255 256 257/********************************************************************//** 258 * @brief Processing for MODBUS function / checking all registers 259 * @param[in] None 260 * @return None 261 *********************************************************************/ 262void Modbus_processing() 263{ 264 holdingRegs[TOTAL_ERRORS] = modbus_update(holdingRegs); 265 266 // update of variables by Modbus 267 if (holdingRegs[ROOM_TEMP] != iRoom_temperature) { 268 if ((holdingRegs[ROOM_TEMP] > 50) || (holdingRegs[ROOM_TEMP] < 5)) { 269 holdingRegs[ROOM_TEMP] = iRoom_temperature; 270 } else { 271 iRoom_temperature = holdingRegs[ROOM_TEMP]; 272 update_Room_temp(); 273 update_circle_color(); 274 } 275 } 276 277 if (holdingRegs[SET_TEMP] != iSet_temperature) { 278 if ((holdingRegs[SET_TEMP] > MAX_TEMPERATURE) || (holdingRegs[SET_TEMP] < MIN_TEMPERATURE)) { 279 holdingRegs[SET_TEMP] = iSet_temperature; 280 } else { 281 iSet_temperature = holdingRegs[SET_TEMP]; 282 update_SET_temp(); 283 update_circle_color(); 284 } 285 } 286 287 288 if (holdingRegs[FAN_LEVEL] != iFan_level) { 289 if ((holdingRegs[FAN_LEVEL] > 5) || (holdingRegs[FAN_LEVEL] < 0)) { 290 holdingRegs[FAN_LEVEL] = iFan_level; 291 } else { 292 iFan_level = holdingRegs[FAN_LEVEL]; 293 draw_fan_level(50,312,iFan_level); 294 } 295 } 296 297 if ((holdingRegs[BEEPER] > 500) && (holdingRegs[BEEPER] < 4000)) { 298 tone(SND_PIN,holdingRegs[BEEPER],100); 299 } 300 holdingRegs[BEEPER] = 0; 301 302} 303 304 305/********************************************************************//** 306 * @brief detecting pressed buttons with the given touchscreen values 307 * @param[in] None 308 * @return None 309 *********************************************************************/ 310void DetectButtons() 311{ 312 // in main program 313 if (PMode == PM_MAIN){ 314 315 // button UP 316 if ((X>190) && (Y<50)) { 317 if (iSet_temperature < MAX_TEMPERATURE) iSet_temperature++; 318 tone(SND_PIN,2000,100); 319 holdingRegs[SET_TEMP] = iSet_temperature; 320 update_SET_temp(); 321 update_circle_color(); 322 } 323 324 // button DWN 325 if ((X>190) && (Y>200 && Y<250)) { 326 if (iSet_temperature > MIN_TEMPERATURE) iSet_temperature--; 327 tone(SND_PIN,2000,100); 328 holdingRegs[SET_TEMP] = iSet_temperature; 329 update_SET_temp(); 330 update_circle_color(); 331 } 332 333 // button FAN MAX 334 if ((X>180) && (Y>270)) { 335 tone(SND_PIN,2000,100); 336 if (iFan_level < 5) iFan_level++; 337 draw_fan_level(50,312,iFan_level); 338 holdingRegs[FAN_LEVEL] = iFan_level; 339 } 340 341 // button FAN MIN 342 if ((X<60) && (Y>270)) { 343 tone(SND_PIN,2000,100); 344 if (iFan_level > 0) iFan_level--; 345 draw_fan_level(50,312,iFan_level); 346 holdingRegs[FAN_LEVEL] = iFan_level; 347 } 348 349 // button gearwheel 350 if ((X<60) && (Y<50)) { 351 draw_option_screen(); 352 PMode = PM_OPTION; 353 } 354 355 356 } else if (PMode == PM_OPTION){ 357 358 // button - 359 if ((X<110) && (Y<75)) { 360 if (Modbus_ID > 0) Modbus_ID--; 361 update_Modbus_addr(); 362 } 363 364 // button + 365 if ((X>130) && (Y<75)) { 366 if (Modbus_ID < 255) Modbus_ID++; 367 update_Modbus_addr(); 368 } 369 370 // button screen cleaning 371 if ((Y>85) && (Y<155)) { 372 tft.fillScreen(ILI9341_BLACK); 373 tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 374 tft.setFont(&FreeSansBold24pt7b); 375 PMode = PM_CLEANING; 376 Timer_Cleaning = 255; 377 } 378 379 380 // button OK 381 if (Y>265) { 382 Thermostat_mode = BOOT; 383 draw_main_screen(); 384 modbus_configure(BAUDRATE, Modbus_ID, 0, TOTAL_REGS_SIZE, 0); 385 PMode = PM_MAIN; 386 } 387 388 389 } 390} 391 392 393 394/********************************************************************//** 395 * @brief Drawing of the main program screen 396 * @param[in] None 397 * @return None 398 *********************************************************************/ 399void draw_main_screen() 400{ 401 tft.fillScreen(ILI9341_BLACK); 402 403 // draw circles 404 update_circle_color(); 405 406 // draw temperature up/dwn buttons 407 draw_up_down_button(); 408 409 // draw icons 410 tft.drawRGBBitmap(10,290, fan_blue_24,24,24); 411 tft.drawRGBBitmap(200,282, fan_blue_32,32,32); 412 tft.drawRGBBitmap(10,10, wrench, 24,24); 413 414 // draw default fan level 415 draw_fan_level(50,312,iFan_level); 416 417 update_SET_temp(); 418 419} 420 421 422 423/********************************************************************//** 424 * @brief Drawing of the screen for Options menu 425 * @param[in] None 426 * @return None 427 *********************************************************************/ 428void draw_option_screen() 429{ 430 tft.fillScreen(ILI9341_BLACK); 431 432 tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 433 tft.setFont(&FreeSansBold9pt7b); 434 435 // Modbus Address adjustment 436 tft.setCursor(10, 20); 437 tft.print("MODBUS address"); 438 tft.setFont(&FreeSansBold24pt7b); 439 tft.setCursor(30, 65); 440 tft.print("-"); 441 tft.setCursor(190, 65); 442 tft.print("+"); 443 tft.drawLine(5,80,235,80, ILI9341_WHITE); 444 445 // Screen cleaning idle timer 446 tft.setFont(&FreeSansBold12pt7b); 447 tft.setCursor(26, 130); 448 tft.print("Screen cleaning"); 449 tft.drawLine(5,160,235,160, ILI9341_WHITE); 450 451 // OK Button 452 tft.setFont(&FreeSansBold24pt7b); 453 tft.drawLine(5,260,235,260, ILI9341_WHITE); 454 tft.setCursor(90, 310); 455 tft.print("OK"); 456 update_Modbus_addr(); 457 458} 459 460 461/********************************************************************//** 462 * @brief update of the value for set temperature on the screen 463 * (in the big colored circle) 464 * @param[in] None 465 * @return None 466 *********************************************************************/ 467void update_SET_temp() 468{ 469 int16_t x1, y1; 470 uint16_t w, h; 471 String curValue = String(iSet_temperature); 472 int str_len = curValue.length() + 1; 473 char char_array[str_len]; 474 curValue.toCharArray(char_array, str_len); 475 tft.fillRect(70, 96, 60, 50, ILI9341_BLACK); 476 tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 477 tft.setFont(&FreeSansBold24pt7b); 478 tft.getTextBounds(char_array, 80, 130, &x1, &y1, &w, &h); 479 tft.setCursor(123 - w, 130); 480 tft.print(char_array); 481} 482 483 484 485/********************************************************************//** 486 * @brief update of the value for room temperature on the screen 487 * (in the small grey circle) 488 * @param[in] None 489 * @return None 490 *********************************************************************/ 491void update_Room_temp() 492{ 493 int16_t x1, y1; 494 uint16_t w, h; 495 String curValue = String(iRoom_temperature); 496 int str_len = curValue.length() + 1; 497 char char_array[str_len]; 498 curValue.toCharArray(char_array, str_len); 499 tft.fillRect(36, 200, 30, 21, ILI9341_ULTRA_DARKGREY); 500 tft.setTextColor(ILI9341_WHITE, ILI9341_ULTRA_DARKGREY); 501 tft.setFont(&FreeSansBold12pt7b); 502 tft.getTextBounds(char_array, 40, 220, &x1, &y1, &w, &h); 503 tft.setCursor(61 - w, 220); 504 tft.print(char_array); 505} 506 507 508 509/********************************************************************//** 510 * @brief update of the color of the big circle according the 511 * difference between set and room temperature 512 * @param[in] None 513 * @return None 514 *********************************************************************/ 515void update_circle_color() 516{ 517 // HEATING 518 if ((iRoom_temperature < iSet_temperature) && (Thermostat_mode != HEATING)) { 519 Thermostat_mode = HEATING; 520 draw_circles(); 521 } 522 523 // COOLING 524 if ((iRoom_temperature > iSet_temperature) && (Thermostat_mode != COOLING)) { 525 Thermostat_mode = COOLING; 526 draw_circles(); 527 } 528 529 // Temperature ok 530 if ((iRoom_temperature == iSet_temperature) && (Thermostat_mode != TEMP_OK)) { 531 Thermostat_mode = TEMP_OK; 532 draw_circles(); 533 } 534} 535 536 537/********************************************************************//** 538 * @brief update of the value for MODBUS ID in the options menu on 539 * the screen 540 * @param[in] None 541 * @return None 542 *********************************************************************/ 543void update_Modbus_addr() 544{ 545 tft.fillRect(110, 30, 60, 45, ILI9341_BLACK); 546 tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 547 tft.setFont(&FreeSansBold24pt7b); 548 tft.setCursor(115, 65); 549 tft.print(Modbus_ID); 550} 551 552 553 554/********************************************************************//** 555 * @brief drawing of the circles in main screen including the value 556 * of room temperature 557 * @param[in] None 558 * @return None 559 *********************************************************************/ 560void draw_circles() 561{ 562 563 //draw big circle 564 unsigned char i; 565 if (iRoom_temperature < iSet_temperature) { 566 // heating - red 567 for(i=0; i < 10; i++) tft.drawCircle(120, 120, 80 + i, ILI9341_RED); 568 } else if (iRoom_temperature > iSet_temperature) { 569 // cooling - blue 570 for(i=0; i < 10; i++) tft.drawCircle(120, 120, 80 + i, ILI9341_BLUE); 571 } else { 572 // Temperature ok 573 for(i=0; i < 10; i++) tft.drawCircle(120, 120, 80 + i, ILI9341_GREEN); 574 } 575 576 //draw small 577 tft.fillCircle(60, 200, 40, ILI9341_ULTRA_DARKGREY); 578 579 //draw °C in big circle 580 tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 581 tft.setFont(&FreeSansBold9pt7b); 582 tft.setCursor(130, 100); 583 tft.print("o"); 584 tft.setFont(&FreeSansBold24pt7b); 585 tft.setCursor(140, 130); 586 tft.print("C"); 587 588 // draw room and °C in small circle 589 tft.setTextColor(ILI9341_WHITE, ILI9341_ULTRA_DARKGREY); 590 tft.setFont(&FreeSansBold12pt7b); 591 tft.setCursor(75, 220); 592 tft.print("C"); 593 tft.drawCircle(69,204, 2, ILI9341_WHITE); 594 tft.drawCircle(69,204, 3, ILI9341_WHITE); 595 tft.setFont(&FreeSansBold9pt7b); 596 tft.setCursor(35, 190); 597 tft.print("Room"); 598 update_Room_temp(); 599 600} 601 602 603 604/********************************************************************//** 605 * @brief drawing of the both buttons for setting temperature up 606 * and down 607 * @param[in] None 608 * @return None 609 *********************************************************************/ 610void draw_up_down_button() 611{ 612 //up button 613 tft.fillTriangle(215,10,230,30,200,30, ILI9341_WHITE); 614 615 //down button 616 tft.fillTriangle(215,230,230,210,200,210, ILI9341_WHITE); 617} 618 619 620 621/********************************************************************//** 622 * @brief drawing of the fan level in main screen 623 * @param[in] None 624 * @return None 625 *********************************************************************/ 626void draw_fan_level(uint16_t x0, uint16_t y0, uint8_t ilevel) 627{ 628 unsigned char i; 629 if (ilevel >= 5) ilevel = 5; 630 for(i=0; i < 5; i++) { 631 if (i < ilevel) { 632 tft.fillRect(x0 + (30*i), y0- 10 -(i*8), 20, 10 + (i*8), ILI9341_WHITE); 633 } else { 634 tft.fillRect(x0 + (30*i), y0- 10 -(i*8), 20, 10 + (i*8), ILI9341_BLACK); 635 tft.drawRect(x0 + (30*i), y0- 10 -(i*8), 20, 10 + (i*8), ILI9341_WHITE); 636 637 } 638 } 639} 640
Comments
Only logged in users can leave comments
hwhardsoft
0 Followers
•0 Projects
Table of contents
Intro
14
0