/*
   DHT22 Humidity controller using MBPIC V2
   (Using MBL as bootloader)
   
   2 inputs to control the humidity setpoint ie up/down
   1 output (relay) pulsed on 2s off 10s (adjustable via rs232 by me)
   hysteresis of control loop set for 1% either way
   
   (c)2016  Jannie Hamman  All rights reserved
   
   V1.0  -  08/02/2016  -  Ported program from 18F88 prototype
*/

#include <18F4520.h>
#device ADC=10
#use delay(crystal=4000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8,stream=PORT1)

//IO defines
#define dht       pin_c3         //IO pin for DHT22 data
#define lcd_bl    pin_b0         //LCD Backlight output
#define humup     pin_a0         //Humidity SP up adjust input
#define humdwn    pin_a1         //Humidity SP down adjust input
#define relay     pin_a2         //Relay for misting solenoid output

//Global defines
int i,hh,hl,th,tl,cs,ccs,cnt;
int16 rhd,temp,humsp,flag,spdh,spdl,jrhd,jtemp,jspdh,jspdl;

#bit neg    =  flag.0            //Negative temp flag
#bit err    =  flag.1            //Sensor error
#bit cse    =  flag.2            //Check sum error
#bit relp   =  flag.3            //Relay pulse
#bit sps    =  flag.4            //SP status ie mist on 1 or off 0

//LCD functions
#define lcd_db7   pin_b7
#define lcd_db6   pin_b6
#define lcd_db5   pin_b5
#define lcd_db4   pin_b4
#define lcd_e     pin_b3
#define lcd_rw    pin_b2
#define lcd_rs    pin_b1

#define lcd_4i_2line    0x28     //2/4 line, 4 bit interface
#define lcd_line1       0x80     //line 1 position 1
#define lcd_line2       0xc0     //line 2 position 1
#define lcd_cls         0x01     //clear display, go home, no shift
#define lcd_home        0x02     //go home, noshift
#define lcd_doff        0x08     //display off
#define lcd_donnc       0x0c     //display on, cursor off
#define lcd_donb        0x0d     //display on, cursor off, blink on
#define lcd_doco        0x0e     //display on, cursor on
#define lcd_docb        0x0f     //display on, cursor on, blink on 
#define lcd_sfdc        0x04     //decrement DD RAM address
#define lcd_shdl        0x05     //shift display left
#define lcd_sfic        0x06     //increment DD RAM address
#define lcd_shdr        0x07     //shifts entire display right
#define lcd_cleft       0x10     //decrement cursor
#define lcd_cright      0x14     //increment cursor
#define lcd_dleft       0x18     //shift whole display left
#define lcd_dright      0x1c     //shift whole display right
#define lcd_cgb         0x40     //CG RAM base address
#define lcd_ddb         0x80     //DD RAM base address

int defchar1[8]={0x04,0x0e,0x15,0x15,0x04,0,0,0};        //Up arrow
int defchar2[8]={0,0,0x04,0x04,0x15,0x0e,0x04,0};        //Down arrow
int defchar3[8]={0x1b,0x1b,0,0x04,0,0x11,0x0e,0};        //Face
int defchar4[8]={0x18,0x18,0x03,0x04,0x04,0x04,0x03,0};  //Degree C symbol
int defchar5[8]={0x03,0x04,0x0B,0x14,0x0B,0x04,0x03,0};  //Mist onp
int defchar6[8]={0,0,0x0A,0x04,0x0A,0,0,0};              //Mist off
int defchar7[8]={0x02,0x04,0x0A,0x14,0x0A,0x04,0x02,0};  //Mist on
int defchar8[8]={0x0e,0x10,0x10,0x1f,0x1b,0x1b,0x1f,0};  //open lock

#define arrowup   8  //Arrow up char \0 is NULL!!!!!
#define arrowdwn  1  //Arrow down char
#define aface     2  //Face (that smile?)
#define degsym    3  //Degree sign
#define okchar    4  //OK char
#define oneontwo  5  //1 on top of 2 char
#define lockclose 6  //Closed padlock
#define lockopen  7  //Opened padlock

int lcd_ln;    //line number of current character
int lcd_data;  //data to and from LCD
#bit lcdd7=lcd_data.7
#bit lcdd6=lcd_data.6
#bit lcdd5=lcd_data.5
#bit lcdd4=lcd_data.4
#bit lcdd3=lcd_data.3
#bit lcdd2=lcd_data.2
#bit lcdd1=lcd_data.1
#bit lcdd0=lcd_data.0
#bit lcdbf=lcd_data.7

//Send a byte to the LCD module
//rs=0=command rs=1=character rs=2=4bit command
void lcd_send_byte(int rs, int data) {
   lcd_data=data;
   output_low(lcd_rs); 
   delay_us(80);  
   if(rs==1)output_high(lcd_rs); 
      else output_low(lcd_rs); 
   delay_us(3); 
   output_low(lcd_rw); 
   delay_us(3); 
   output_low(lcd_e);
   output_bit(lcd_db7,lcdd7);
   output_bit(lcd_db6,lcdd6);
   output_bit(lcd_db5,lcdd5);
   output_bit(lcd_db4,lcdd4); 
   delay_us(3); 
   output_high(lcd_e); 
   delay_us(4); 
   output_low(lcd_e);
   output_bit(lcd_db7,lcdd3);
   output_bit(lcd_db6,lcdd2);
   output_bit(lcd_db5,lcdd1);
   output_bit(lcd_db4,lcdd0);
   delay_us(3); 
   output_high(lcd_e); 
   delay_us(4); 
   output_low(lcd_e); 
}

//Read a byte from the LCD module
//Returns 0 if RW=0 (connected to ground)
int lcd_read_byte(void){ 
   lcd_data=0;
   output_high(lcd_rw); 
   delay_us(1); 
   output_high(lcd_e); 
   delay_us(3); 
   lcdd4 = input(lcd_db4); 
   lcdd5 = input(lcd_db5); 
   lcdd6 = input(lcd_db6); 
   lcdd7 = input(lcd_db7); 
   output_low(lcd_e);
   delay_us(3); 
   output_high(lcd_e); 
   delay_us(3);   
   lcdd0 = input(lcd_db4); 
   lcdd1 = input(lcd_db5); 
   lcdd2 = input(lcd_db6); 
   lcdd3 = input(lcd_db7); 
   output_low(lcd_e); 
   return(lcd_data); 
} 

//Initialize LCD module
//!!! Must be executed before using the CLCD !!!
void lcd_init(void){
   output_low(lcd_rs); 
   output_low(lcd_e); 
   output_low(lcd_rw); 
   delay_ms(16); 
   lcd_send_byte(2,0x03);
   delay_ms(6);
   lcd_send_byte(2,0x03);
   delay_us(260); 
   lcd_send_byte(2,0x03);
   delay_ms(260); 
   lcd_send_byte(2,0x02);
   lcd_send_byte(0,lcd_4i_2line);

   lcd_send_byte(0,lcd_donnc);      //display on, no cursor
   lcd_send_byte(0,lcd_sfic);       //auto incr cursor
   lcd_send_byte(0,lcd_cls);        //clear screen and go home
   delay_ms(6); 
}

//Write user chars to CGRAM
void lcd_send_user(){
   int i;
   lcd_send_byte(0,0x40);     //CGRAM start = user defined char 1
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar1[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar2[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar3[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar4[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar5[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar6[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar7[i]);}
   for(i=0;i<=7;i++){
      lcd_send_byte(1,defchar8[i]);}
}

//Define on-the-fly user character 0
//Cursor (row7) defaults to 0
//Used for animation
void lcd_def_char(int r0,int r1,int r2,int r3,int r4,int r5,int r6){
   lcd_send_byte(0,0x40);     //CGRAM start
   lcd_send_byte(1,r0);
   lcd_send_byte(1,r1);
   lcd_send_byte(1,r2);
   lcd_send_byte(1,r3);
   lcd_send_byte(1,r4);
   lcd_send_byte(1,r5);
   lcd_send_byte(1,r6);
   lcd_send_byte(1,0);     //Row 7 is for cursor
}

//Change display position for next character to be written
//x=position in line y
void lcd_goto_pos(int x, int y){ 
   int pos; 
   switch(y){ 

      case 1:pos=lcd_line1; break;
      case 2:pos=lcd_line2; break;

   }
   pos+=(x-1); 
   lcd_send_byte(0,lcd_ddb|pos); 
   delay_ms(1);
}

//Finally, the routine to write to the display
//Usage: lcd_putc('X') or lcd_putc("\fLine 1\nLine 2\nLine 3\nLine 4")
void lcd_putc(int x){ 
   switch(x) { 
      case '\f':lcd_send_byte(0,lcd_cls); lcd_ln=1; delay_ms(3); break; 
      case '\n':lcd_goto_pos(1,++lcd_ln); break; 
      case '\b':lcd_send_byte(0,lcd_cleft); break;
      default:lcd_send_byte(1,x); break; 
   } 
} 

//DHT-22 FUNCTIONS
//Determine pulse logic based on length
int datain(){
   int rddata=0;
   for(i=0;i<=7;i++){
      err=false;                       //no error if waiting <1ms
      set_timer1(0);                   //error check start and exit if longer
      while(!input(dht)&&!err);        //wait for line to go high
      delay_us(30);                    //high<28us means bit is 0
      if(input(dht)){                  //if still high bit is 1
         set_timer1(0);                //make counter 0
         err=false;                    //no error yet
         bit_set(rddata,7-i);          //data is 1
         while(input(dht)&&!err);      //wait for line to go low   
      }
   }
   return rddata;
}

//Init and read sensor
void rd_dht(){
   output_float(dht);               //make the pin an input
   delay_ms(250);                   //wait a bit
   output_low(dht);                 //start init sequence
   delay_ms(25);                    //keep low for 25ms
   output_high(dht);
   delay_us(30);                    //keep high for 30us
   output_float(dht);               //pin is now an input
   err=false;                       //no error if waiting <1ms
   set_timer1(0);                   //error check start and exit if longer   
   while(!input(dht)&&!err);        //wait for sensor to pull line high
   err=false;                       //no error if waiting <1ms
   set_timer1(0);                   //error check start and exit if longer   
   while(input(dht)&&!err);         //wait for sensor to pull line low
                                    //this is the start of the transmission   
   //The next 40 transistion high times determines 0 or 1 and needs to be timed
   //Thus we need to read 5 bytes
   
   //Read HH
   hh=datain();
   
   //Read HL
   hl=datain();  
   
   //Read TH
   th=datain();
   
   //Read TL
   tl=datain();
  
   //Read CS
   cs=datain();
   
   //Calculate checksum and flag if incorrect
   ccs=(hh+hl+th+tl)&0xff;
   cse=false;
   if(cs!=ccs)cse=true;
   
   //Make relative humidity an int16. Need to divide by 10 to get genuine RHD
   rhd=make16(hh,hl);
   
   //Check if temperature is negative or not
   neg=false;
   if(bit_test(th,7)){
      neg=true;
      bit_clear(th,7);
   }
   
   //Make temperature an int16. Need to divide by 10 still!
   temp=make16(th,tl);

}

//Display error screen
void serr(void){
   printf(lcd_putc,"\fHC-22 HUMID CNTR");
   printf(lcd_putc,"\nSENSOR IN ERROR!");
}

//Display ego screen
void ego(void){
   printf(lcd_putc,"\fHC-22 HUMID CNTR");
   printf(lcd_putc,"\n(c)2016 J Hamman");
}

//Display the startup screen
void strt(void){
   printf(lcd_putc,"\fRH:    %%  SP:  %%");
   printf(lcd_putc,"\nTmp:       Mist ");  
   lcd_goto_pos(10,2);
   lcd_putc(0x03);
}

#INT_TIMER0
void  TIMER0_isr(void){       //1s overflow

   if(input(humup))humsp+=10;
   if(humsp>=990)humsp=990;
   if(input(humdwn))humsp-=10;
   if(humsp<=1)humsp=0;
   
   spdh=humsp/100;
   spdl=(humsp-(spdh*100))/10;
   
   if(sps){
      if(cnt==0)relp=false;
      if(cnt==1)relp=true;
      if(cnt==4)relp=false;
      cnt++;
      if(cnt==10){
         cnt=0;
//         if(!cse)strt();
      }
   }
   
   if(relp)output_high(relay);
      else output_low(relay);
   if(cse){
         lcd_goto_pos(4,1);
         printf(lcd_putc,"%03.1f",(float)(jrhd)/10.0);
         lcd_goto_pos(14,1);
         printf(lcd_putc,"%lu%lu",jspdh,jspdl);
         lcd_goto_pos(5,2);
         if(neg)lcd_putc("-");else lcd_putc("+");
         printf(lcd_putc,"%02.1f",(float)(jtemp)/10.0);   
   }
      else{
         lcd_goto_pos(4,1);
         printf(lcd_putc,"%03.1f",(float)(rhd)/10.0);
         lcd_goto_pos(14,1);
         printf(lcd_putc,"%lu%lu",spdh,spdl);
         lcd_goto_pos(5,2);
         if(neg)lcd_putc("-");else lcd_putc("+");
         printf(lcd_putc,"%02.1f",(float)(temp)/10.0);
         jrhd=rhd;
         jtemp=temp;
         jspdh=spdh;
         jspdl=spdl;
      }
   //now add output status symbols (user defined 5 & 6 & 7)
   lcd_goto_pos(16,2);
   if(!sps)lcd_putc(0x05);
   if(sps&&relp)lcd_putc(0x04);else lcd_putc(0x06);    
   
}

#INT_TIMER1
void TIMER1_isr(void){        //65ms overflow
    if(!err)err=true;
}

void main()
{
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_16);      //1.0 s overflow
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);      //65.5 ms overflow

   lcd_init();
   lcd_send_user();   
   delay_ms(1000);
   output_high(lcd_bl);
   humsp=750;                  //Default humidity set at 75%
   flag=0;
   cnt=0;
   spdh=spdl=0;
   
   
   ego();
   
   delay_ms(5000);
   
   strt();
   
   delay_ms(1000);
   
   enable_interrupts(INT_TIMER0);
   enable_interrupts(INT_TIMER1);
   enable_interrupts(GLOBAL);
   
   while(TRUE)
   {
      //Get the sensor readings every 1.5s and do sp calc
      rd_dht();
      if(rhd>=(humsp+10)){sps=false;cnt=0;}
      if(rhd<=(humsp-10))sps=true;

      delay_ms(1500);
      
   }

}
