#include <OpenTherm.h> #include <LCD5110_Basic.h> #include <OneWire.h> #include <dht.h> #include <dallastemp.h> #include <avr/eeprom.h> //#include <Power.h> #define DHT22_PIN 14 #define LCD_IDLE 0 #define LCD_MAIN 1 #define LCD_MENU 2 #define LCD_CONFIG_1 11 #define LCD_CONFIG_2 12 #define LCD_CONFIG_3 13 #define LCD_INFO_1 21 #define LCD_INFO_2 22 #define LCD_STATISTICS_1 31 #define LCD_STATISTICS_2 32 #define LCD_ITEM_MODE 1 #define LCD_ITEM_CH_EN 2 #define LCD_ITEM_DHW_EN 3 #define LCD_ITEM_CH_MAX 4 #define LCD_ITEM_CH 5 #define LCD_ITEM_DHW 6 #define LCD_ITEM_ROOM 7 #define LCD_ITEM_BRIGH 8 #define LCD_ITEM_ACTIVE 9 #define LCD_ITEM_MAX_MODULATION 10 #define LCD_ITEM_DAYS 11 #define LCD_ITEM_HOURS 12 #define LCD_ITEM_MINUTES 13 #define LCD_ITEM_KP 14 #define LCD_ITEM_KI 15 dht internal_s; OneWire ow(A1); Dallastemp external_s(&ow); OpenTherm ot(8,7); LCD5110 lcd(13,11,10,12,0); //Power sleep; extern uint8_t SmallFont[]; extern uint8_t MediumNumbers[]; extern uint8_t BigNumbers[]; //Encoder handling volatile uint32_t ts_enc=0; volatile int8_t encoder=0; //Clock handling uint32_t clock_ts=0,clock_delta=0; uint8_t hour=0,minute=0,second=0,day=0; //Menu Handling uint8_t menu,item; int8_t pos=0; const char* day_names[7]={,,,,,,}; const char* mode_names[2]={,}; //Misc uint8_t display_enabled,cfg_enabled,button1=0,button2=0,item_tmp=0,update_period=0; float iSum; struct thermostat_config{ uint8_t address[8]; ot_init_settings ot_settings; float indoor_target_temp; uint8_t active_time; uint8_t brightness; uint8_t mode; uint8_t Kp; uint8_t Ki; uint8_t reserved[8]; }; //thermostat_config settings={{0x28,0xFF,0x53,0x76,0x60,0x14,0x02,0xFC},{1,1,70,30.0,40},22.0,30,130,1}; thermostat_config settings; /* ISR(INT0_vect){ ot.extIntHandler(); } */ ISR(PCINT0_vect){ ot.extIntHandler(); } ISR(PCINT1_vect){ ot.extIntHandler(); } ISR(PCINT2_vect){ ot.extIntHandler(); } ISR(TIMER2_COMPA_vect){ ot.timer2CompAHandler(); } ISR(TIMER2_COMPB_vect){ ot.timer2CompBHandler(); } ISR(WDT_vect) { // sleep.watchdogEvent(); } ISR(INT1_vect){ display_enabled=settings.active_time; OCR1A=settings.brightness; if((millis()-ts_enc >20) && cfg_enabled) { ts_enc=millis(); if ((PIND&bit(4))) { encoder++; } else { encoder--; } } } void fade_display(){ for(uint8_t i=settings.brightness;i>10;i-=10){ OCR1A=i; delay(20); } OCR1A=0; } void enc_setup(){ cli(); DDRD&=~(bit(3)|bit(4)); //set A and B to input EIMSK|=bit(INT1); EICRA|=bit(ISC11); EICRA&=~bit(ISC10); EIFR|=bit(INTF1); sei(); } void button_setup(){ DDRC&=~(bit(2)|bit(3)); } void read_config(){ eeprom_read_block(&settings, 0, sizeof(thermostat_config)); } void write_config(){ eeprom_write_block(&settings, 0, sizeof(thermostat_config)); } void lcd_idle(){ char mod_lev[2]={'\0','\0'}; lcd.setFont(BigNumbers); lcd.print(,24,0); lcd.print(,0,24);// - thermometer icon lcd.print(,0,0); lcd.print(,34,0); lcd.print(,42,24);// - House icon lcd.printNumI(hour,(hour>9)?0:14,0); lcd.printNumI(minute,(minute>9)?34:48,0); lcd.setFont(SmallFont); lcd.print(day_names[day],62,16); lcd.printNumF(internal_s.temperature,1,60,40); switch(ot.status&0x7){ case OT_STATUS_CH: lcd.print(,78,32); break; case OT_STATUS_DHW: lcd.print(,78,32); break; case OT_STATUS_FAULT: lcd.print(,72,32); break; }; if (ot.status&0x8){ if (ot.modulation < 34) *mod_lev='^'; else if (ot.modulation < 67) *mod_lev='_'; else *mod_lev='`'; lcd.print(mod_lev,72,32); } lcd.setFont(MediumNumbers); lcd.printNumF(external_s.getTemp(settings.address),0,6,32); lcd.printNumI(internal_s.temperature,48,32); } void lcd_main(){ lcd.print(,LEFT,0); lcd.printNumF(internal_s.temperature,1,30,0); if (settings.mode) lcd.printNumF(settings.indoor_target_temp,1,60,0); else lcd.print(,54,0); lcd.print(,LEFT,8); lcd.printNumF(internal_s.humidity,1,24,8); lcd.print(,LEFT,16); lcd.printNumF(external_s.getTemp(settings.address),1,48,16); lcd.print(,LEFT,24); lcd.printNumF(ot.CH,1,30,24); lcd.printNumF(settings.ot_settings.CH_temp,1,60,24); lcd.print(,LEFT,32); lcd.printNumF(ot.DHW,1,24,32); lcd.printNumF(ot.target_DHW,1,54,32); switch(ot.status&0x7){ case OT_STATUS_CH: lcd.print(,LEFT,40); break; case OT_STATUS_DHW: lcd.print(,LEFT,40); break; case OT_STATUS_FAULT: lcd.print(,LEFT,40); switch(ot.fault&0x3D){ case OT_FAULT_SERVICE: lcd.print(,LEFT,40); break; case OT_FAULT_LOW_WATER: lcd.print(,LEFT,40); break; case OT_FAULT_GAS: lcd.print(,LEFT,40); break; case OT_FAULT_AIR_PRESSURE: lcd.print(,LEFT,40); break; case OT_FAULT_WATER_OV_TEMP: lcd.print(,LEFT,40); break; } break; default: lcd.print(,LEFT,40); break; }; if (ot.status&0x8) { lcd.print(,24,40); lcd.printNumI(ot.modulation,60,40); } } void lcd_menu(){ lcd.print(,LEFT,0); lcd.print(,LEFT,8); lcd.print(,LEFT,16); } void lcd_config_1(){ lcd.print(,LEFT,0); lcd.print(mode_names[(item==LCD_ITEM_MODE)?item_tmp:settings.mode],RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI((item==LCD_ITEM_CH_EN)?item_tmp:ot.CH_enabled,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumI((item==LCD_ITEM_DHW_EN)?item_tmp:ot.DHW_enabled,RIGHT,16); lcd.print(,LEFT,24); lcd.printNumI((item==LCD_ITEM_CH_MAX)?item_tmp:ot.CH_max,RIGHT,24); lcd.print(,LEFT,32); lcd.printNumI((item==LCD_ITEM_CH)?item_tmp:ot.target_CH,RIGHT,32); lcd.print(,LEFT,40); lcd.printNumI((item==LCD_ITEM_DHW)?item_tmp:ot.target_DHW,RIGHT,40); } void lcd_config_2(){ lcd.print(,LEFT,0); lcd.printNumF((item==LCD_ITEM_ROOM)?(float)item_tmp/10+10:settings.indoor_target_temp,1,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI((item==LCD_ITEM_BRIGH)?item_tmp:settings.brightness,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumI((item==LCD_ITEM_ACTIVE)?item_tmp:settings.active_time,RIGHT,16); lcd.print(,LEFT,24); lcd.printNumI((item==LCD_ITEM_MAX_MODULATION)?item_tmp:settings.ot_settings.max_modulation,RIGHT,24); lcd.print(,LEFT,32); lcd.print(day_names[(item==LCD_ITEM_DAYS)?item_tmp:day],RIGHT,32); lcd.print(,LEFT,40); lcd.printNumI((item==LCD_ITEM_HOURS)?item_tmp:hour,RIGHT,40); } void lcd_config_3(){ lcd.print(,LEFT,0); lcd.printNumI((item==LCD_ITEM_MINUTES)?item_tmp:minute,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI((item==LCD_ITEM_KP)?item_tmp:settings.Kp,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumI((item==LCD_ITEM_KI)?item_tmp:settings.Ki,RIGHT,16); } void lcd_info_1(){ lcd.print(,LEFT,0); lcd.printNumF(ot.CH_water_pressure,1,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumF(ot.DHW_flow,1,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumF(ot.CH_return_temp,1,RIGHT,16); lcd.print(,LEFT,24); lcd.printNumI(ot.max_capacity,RIGHT,24); lcd.print(,LEFT,32); lcd.printNumI(ot.min_modulation,RIGHT,32); lcd.print(,LEFT,40); lcd.printNumI(ot.DHW_min_lim,RIGHT,40); } void lcd_info_2(){ lcd.print(,LEFT,0); lcd.printNumI(ot.DHW_max_lim,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI(ot.CH_min_lim,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumI(ot.CH_max_lim,RIGHT,16); lcd.print(,LEFT,24); lcd.printNumF(iSum,1,RIGHT,24); } void lcd_stats_1(){ lcd.print(,LEFT,0); lcd.printNumI(ot.burner_starts,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI(ot.CH_pump_starts,RIGHT,8); lcd.print(,LEFT,16); lcd.printNumI(ot.DHW_pump_starts,RIGHT,16); lcd.print(,LEFT,24); lcd.printNumI(ot.DHW_burner_starts,RIGHT,24); lcd.print(,LEFT,32); lcd.printNumI(ot.burner_op_hours,RIGHT,32); lcd.print(,LEFT,40); lcd.printNumI(ot.CH_pump_op_hours,RIGHT,40); } void lcd_stats_2(){ lcd.print(,LEFT,0); lcd.printNumI(ot.DHW_pump_op_hours,RIGHT,0); lcd.print(,LEFT,8); lcd.printNumI(ot.DHW_burner_op_hours,RIGHT,8); } void update_clock(){ uint32_t tmp=millis()-clock_ts; clock_delta+=(tmp>0)?tmp:0; clock_ts=millis(); while (clock_delta >= 1000){ second++; clock_delta-=1000; if (second > 59){ second=0; minute++; if (minute > 59){ minute=0; hour++; if (hour > 23){ hour=0; day++; if (day>6) day=0; } } } } } void update_display(){ lcd.clrScr(); lcd.setFont(SmallFont); if (menu != LCD_MAIN && menu != LCD_IDLE) { if (pos>5){ pos=0; switch(menu){ case LCD_INFO_1: menu=LCD_INFO_2; break; case LCD_STATISTICS_1: menu=LCD_STATISTICS_2; break; case LCD_CONFIG_1: menu=LCD_CONFIG_2; break; case LCD_CONFIG_2: menu=LCD_CONFIG_3; break; default: pos=5; } } if (pos<0){ pos=5; switch(menu){ case LCD_INFO_2: menu=LCD_INFO_1; break; case LCD_STATISTICS_2: menu=LCD_STATISTICS_1; break; case LCD_CONFIG_2: menu=LCD_CONFIG_1; break; case LCD_CONFIG_3: menu=LCD_CONFIG_2; break; default: pos=0; } } }; switch(menu){ case LCD_IDLE: lcd_idle(); break; case LCD_MAIN: lcd_main(); break; case LCD_MENU: lcd_menu(); break; case LCD_CONFIG_1: lcd_config_1(); break; case LCD_CONFIG_2: lcd_config_2(); break; case LCD_CONFIG_3: lcd_config_3(); break; case LCD_INFO_1: lcd_info_1(); break; case LCD_INFO_2: lcd_info_2(); break; case LCD_STATISTICS_1: lcd_stats_1(); break; case LCD_STATISTICS_2: lcd_stats_2(); break; }; if (menu != LCD_IDLE && menu != LCD_MAIN) if(! item) lcd.print(,LEFT,pos*8); else lcd.print(,LEFT,pos*8); } void setup(){ lcd.InitLCD(); lcd.setFont(SmallFont); lcd.clrScr(); enc_setup(); button_setup(); read_config(); //sleep.measure_wdt(1); // external_s.setRes(settings.address,TEMP_11_BIT); // external_s.startConv(settings.address); analogWrite(9,settings.brightness); display_enabled=settings.active_time; lcd.print(,CENTER,16); ot.begin(&settings.ot_settings); //sleep.measure_wdt(0); lcd.clrScr(); lcd.print(,CENTER,0); lcd.printNumF(ot.slave_ver,1,CENTER,8); lcd.print(,CENTER,16); lcd.printNumI(ot.member_id,CENTER,24); lcd.print(,CENTER,32); lcd.printNumI(ot.sl_cfg,CENTER,40); } void loop(){ //DEBUG //uint8_t type=0,id=0; //uint16_t data=0; float ext_temp,int_temp,t_error; if (!update_period) { internal_s.read22(DHT22_PIN); external_s.startConv(settings.address); } if(display_enabled){ OCR1A=settings.brightness; if(! --display_enabled) { fade_display(); cfg_enabled=0; menu=LCD_IDLE; item=0; delay(1000); ot.update(0); //main thread update_display(); } } if (cfg_enabled) delay (100); else delay(1000); //sleep 1s here // else sleep.sleep(); if (cfg_enabled || !update_period) { update_display(); } if ( (PINC&bit(2)) && button1) button1=0; //if button released reset state if ( (PINC&bit(3)) && button2) button2=0; //Esc if (! (PINC&bit(2)) && ! button1) { button1=1; display_enabled=settings.active_time; OCR1A=settings.brightness; cfg_enabled=1; switch(menu){ case LCD_IDLE: menu=LCD_MAIN; break; case LCD_MAIN: if ((ot.status&0x7) == OT_STATUS_FAULT) { delay(1000); ot.communicate(1,4,256); } break; case LCD_MENU: menu=LCD_MAIN; break; case LCD_CONFIG_1: case LCD_CONFIG_2: case LCD_CONFIG_3: if(! item) menu=LCD_MENU; else item=0; break; default: menu=LCD_MENU; break; }; pos=0; } //Enter if (! (PINC&bit(3)) && ! button2) { button2=1; display_enabled=settings.active_time; OCR1A=settings.brightness; cfg_enabled=1; if (! item){ //standart menu navigation switch(menu){ case LCD_IDLE: case LCD_MAIN: menu=LCD_MENU; break; case LCD_MENU: switch(pos){ case 0: menu=LCD_CONFIG_1; break; case 1: menu=LCD_INFO_1; delay(1000); ot.update(18); //start to get info break; case 2: menu=LCD_STATISTICS_1; delay(1000); ot.update(116); //start to get stats break; } break; case LCD_CONFIG_1: if (!item) item=pos+1; case LCD_CONFIG_2: if (!item) item=pos+7; case LCD_CONFIG_3: if (!item) item=pos+13; switch(item){ case LCD_ITEM_MODE: item_tmp=settings.mode; break; case LCD_ITEM_CH_EN: item_tmp=ot.CH_enabled; break; case LCD_ITEM_DHW_EN: item_tmp=ot.DHW_enabled; break; case LCD_ITEM_CH_MAX: item_tmp=ot.CH_max; break; case LCD_ITEM_CH: item_tmp=settings.ot_settings.CH_temp; break; case LCD_ITEM_DHW: item_tmp=ot.target_DHW; break; case LCD_ITEM_ROOM: item_tmp=(uint8_t)(settings.indoor_target_temp*10 - 100); break; case LCD_ITEM_BRIGH: item_tmp=settings.brightness; break; case LCD_ITEM_ACTIVE: item_tmp=settings.active_time; break; case LCD_ITEM_MAX_MODULATION: item_tmp=settings.ot_settings.max_modulation; break; case LCD_ITEM_DAYS: item_tmp=day; break; case LCD_ITEM_HOURS: item_tmp=hour; break; case LCD_ITEM_MINUTES: item_tmp=minute; break; case LCD_ITEM_KP: item_tmp=settings.Kp; break; case LCD_ITEM_KI: item_tmp=settings.Ki; break; } break; case LCD_INFO_1: break; case LCD_INFO_2: break; }; if (!item) pos=0; } else { //item save switch(item){ case LCD_ITEM_MODE: settings.mode=item_tmp; break; case LCD_ITEM_CH_EN: ot.CH_enabled=item_tmp; settings.ot_settings.CH_enabled=item_tmp; break; case LCD_ITEM_DHW_EN: ot.DHW_enabled=item_tmp; settings.ot_settings.DHW_enabled=item_tmp; break; case LCD_ITEM_CH_MAX: ot.CH_max=item_tmp; settings.ot_settings.CH_max_temp=item_tmp; delay(1000); ot.update(57); break; case LCD_ITEM_CH: settings.ot_settings.CH_temp=item_tmp; ot.target_CH=item_tmp; delay(1000); ot.update(1); break; case LCD_ITEM_DHW: ot.target_DHW=item_tmp; settings.ot_settings.DHW_temp=item_tmp; delay(1000); ot.update(56); break; case LCD_ITEM_ROOM: settings.indoor_target_temp=(float)item_tmp/10+10.0; break; case LCD_ITEM_BRIGH: settings.brightness=item_tmp; break; case LCD_ITEM_ACTIVE: settings.active_time=item_tmp; break; case LCD_ITEM_MAX_MODULATION: ot.max_modulation=item_tmp; settings.ot_settings.max_modulation=item_tmp; delay(1000); ot.update(14); break; case LCD_ITEM_DAYS: day=item_tmp; break; case LCD_ITEM_HOURS: hour=item_tmp; break; case LCD_ITEM_MINUTES: minute=item_tmp; break; case LCD_ITEM_KP: settings.Kp=item_tmp; break; case LCD_ITEM_KI: settings.Ki=item_tmp; break; } write_config(); item_tmp=0; item=0; } } /* ot.complete(&type,&id,&data); lcd.setFont(SmallFont); lcd.print(,LEFT,40); lcd.printNumI(type,0,40); lcd.printNumI(id,12,40); lcd.printNumI(data,36,40); //debug */ if (encoder !=0 ) { if (menu != LCD_MAIN) if(!item) pos=constrain(pos+encoder,-1,6); else switch (item){ case LCD_ITEM_MODE: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_CH_EN: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_DHW_EN: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_CH_MAX: item_tmp=constrain(item_tmp+encoder,ot.CH_min_lim,ot.CH_max_lim); break; case LCD_ITEM_CH: item_tmp=constrain(item_tmp+encoder,ot.CH_min_lim,ot.CH_max_lim); break; case LCD_ITEM_DHW: item_tmp=constrain(item_tmp+encoder,ot.DHW_min_lim,ot.DHW_max_lim); break; case LCD_ITEM_ROOM: item_tmp=constrain(item_tmp+encoder,50,180); break; case LCD_ITEM_BRIGH: item_tmp=constrain(item_tmp+encoder*10,0,255); break; case LCD_ITEM_ACTIVE: item_tmp=constrain(item_tmp+encoder,10,100); break; case LCD_ITEM_MAX_MODULATION: item_tmp=constrain(item_tmp+encoder,10,100); break; case LCD_ITEM_DAYS: item_tmp=constrain(item_tmp+encoder,0,6); break; case LCD_ITEM_HOURS: item_tmp=constrain(item_tmp+encoder,0,23); break; case LCD_ITEM_MINUTES: item_tmp=constrain(item_tmp+encoder,0,59); break; case LCD_ITEM_KP: case LCD_ITEM_KI: item_tmp=constrain(item_tmp+encoder,0,255); break; item_tmp=constrain(item_tmp+encoder,0,255); break; } else if (settings.mode) settings.indoor_target_temp=constrain(settings.indoor_target_temp+encoder*0.1,15.0,28.0); else settings.ot_settings.CH_temp+=encoder*0.1; encoder=0; }; if (menu == LCD_IDLE && ! cfg_enabled && !update_period--) { update_period=60; update_clock(); int_temp=internal_s.temperature; ext_temp=external_s.getTemp(settings.address); t_error=settings.indoor_target_temp-int_temp; iSum=constrain(iSum+t_error,-ot.CH_max_lim,ot.CH_max_lim); if (settings.mode) settings.ot_settings.CH_temp=1*(20.0 + (settings.indoor_target_temp-ext_temp)) + settings.Kp*t_error + settings.Ki*iSum/256; if (settings.ot_settings.CH_temp < ot.CH_min_lim) ot.CH_enabled=0; else ot.CH_enabled = 1; settings.ot_settings.CH_temp=constrain(settings.ot_settings.CH_temp,ot.CH_min_lim,ot.CH_max_lim); if ((!settings.mode && ot.target_CH != settings.ot_settings.CH_temp ) || (settings.mode && abs(ot.target_CH - settings.ot_settings.CH_temp) > 0.5)){ ot.target_CH=settings.ot_settings.CH_temp; // delay(1000); ot.update(1); return; } } ot.update(); }