program Thermostaat;

// D. Rosseel
// Original: 13-6-2008
// Latest update: 06-08-2008

   { --------------------- Port assignment PIC16F628A ---------------------- //

   PortA:                                             PIC pin
     Bit0: DS 1820 (input/output)                        17
     Bit1: Push button Temp+ (input, active high)        18
     Bit2: Push button Temp- (input, active high)         1
     Bit3: Push button Mode  (input, active high)         2
     Bit4: I2C SDA (if used)                              3
     Bit5: --                                             4
     Bit6: I2c SCL (if used)                             15
     Bit7: Relay (output, active high)                   16

   PortB: occupied by the LCD display (See unit LCD1602_4bits)

   }

uses LCD1602_4bits, EepromVariable;
   
var Cnt, Diff, CurrentTemp, SetTemp, PrevSetTemp, Mode: byte;
    TimeState: byte; // runs from 0 to MAXTIMESTATE, each state takes one minute of time
    TimeCnt  : byte; // runs from 0 to 119, each state is approx. 0.5 secs long
    Convert, AcceptTemp, DisplayTemp: boolean;
    Ch: Char;
    I, Crc: byte;
    Buff: array[0..8] of byte;
    CrcErrors: word;
    
const MAXTIMESTATE = 3; // 0..3, 4 states = proportional control during the last 2 degrees

procedure Interrupt;
begin
  if PIR1.TMR1IF then // timer 1 overflow (500 ms elapsed)
  begin
    Inc(TimeCnt);

    if TimeCnt = 7
    then Convert := true;     // start temperature conversion, every minute
                              // 1,5 seconds before reading it ("AcceptTemp")

    if TimeCnt = 10
    then AcceptTemp := true;  // accept measured temperature every minute, first time after 5 seconds
    
    if TimeCnt = 120 then // 1 minute elapsed
    begin
      TimeCnt := 0;
      if TimeState < MAXTIMESTATE
      then Inc(TimeState)
      else TimeState := 0;
    end;
       
    PIR1.TMR1IF      := 0;  // clear interrupt flag
  end;
end;

procedure ShowTemp(Temp: byte);
var Text: string[3];
    Ch: Char;
begin
       Ch := '0';

       if Temp.0                         // fractional part
       then Ch := '5';

       Temp := Temp shr 1;               // get temp value

       ByteToStr(Temp, Text);            // whole number Temp value
       LCD1602Write(Text);
       Lcd1602WriteChar(',');
       Lcd1602WriteChar(Ch);              // decimal value
       LCD1602WriteChar(0);               // degree character
end;

function CalcCrc: byte;
var I, J: byte;
    TmpBit: boolean;
begin
  Result := 0;  // temporary CRC

   for I := 0 to 8 do  // for each byte
   begin
     for J := 0 to 7 do // for each bit
     begin
       TmpBit := Result.0 xor Buff[I].J;         // XOR bit0 of data byte and temporary crc
       Result := Result shr 1;                   // Shift right the temporary CRC byte
       if TmpBit then Result := Result xor $8c;  // If set, account for EXOR feedback

       { equals to:
       TmpBit := Buff[I].J xor Result.0;         // XOR bit0 of data byte and temporary crc
       if TmpBit then Result := Result xor $18;  // If set, account for EXOR feedback
       Result := Result shr 1;                   // Shift right the temporary CRC byte
       Result.7 := TmpBit;                       // Test bit rotates into temporary CRC bit 7
       }
       
     end;
   end;
end;


begin // main

  CMCON  := 7;             // Disable Comparator module's
  PORTA  := 0;             // initialize portA, relay off
  TRISA  := %01111111;

  // ---- Configure LCD ---- //
  LCD1602Init;
  LCD1602WriteCGRam(0, 28,20,28,0,0,0,0,0);   // "degree" character
  LCD1602WriteCGRam(3, 0,0,0,0,0,0,8,24);     // 25% power
  LCD1602WriteCGRam(4, 0,0,0,0,4,4,12,28);    // 50% power
  LCD1602WriteCGRam(5, 0,0,2,2,6,6,14,30);    // 75% power
  LCD1602WriteCGRam(6, 1,1,3,3,7,7,15,31);    // 100% power
  LCD1602Clear;
  
  // ---- Configure timer 1 ---- //
  T1Con.TMR1CS     := 0; // Timer1 Clock Source Select bit, 0 = Internal clock (FOSC/4)
  T1Con.NOT_T1SYNC := 1; // Do not synchronize external clock input
  T1Con.T1CKPS1    := 1; // 1:8 prescaler  , TMR 1 overflow every 1.9 Hz (= 500 ms timebase)
  T1Con.T1CKPS0    := 1; //
  T1Con.T1OSCEN    := 0; // LP Oscillator Enable Control bit
  T1Con.TMR1ON     := 1; // Timer 1 enabled
  PIE1.TMR1IE      := 1; // TMR1 Overflow Interrupt Enable bit
   
  TMR1H := 0;
  TMR1L := 0;
  PIR1.TMR1IF      := 0;  // clear timer 1 interrupt flag
   
  // ---- Setup measurements --- /
  Mode := EEProm_read(5);        // Read "mode"
  if Mode > 1 then Mode := 0;
  SetTemp := EEprom_read(Mode);  // Read set temperature of "Mode"
  if SetTemp > 60                // 30 degrees
  then SetTemp := 60;
  
  CurrentTemp := SetTemp;        // Initial value of current temp
  PrevSetTemp := SetTemp;

  Cnt := 0;                      // restart "same value" counting

  // initialise variables
  TimeState := 0;
  TimeCnt:= 0;
  Convert := false;
  AcceptTemp := false;
  DisplayTemp := false;
  Crc := 0;
  CrcErrors := 0;
   
  // --- enable interrupts and go! ---
  IntCon.PEIE     := 1; // Peripheral Interrupt Enable bit
  Intcon.GIE      := 1; // Global Interrupt Enable bit
   
  repeat

    // --------------------- Get Buttons ------------------------- //

    if not Convert then  // start temperature conversion can not wait
    begin
       
      if Button(PortA, 1, 100, 1) then // Temp up key
      begin
        if SetTemp < 60                // 30 degrees
        then Inc(SetTemp);
        EEprom_Write(Mode, SetTemp);  // wait for the key release
        Delay_ms(350);
      end;

      if Button(PortA, 2, 100, 1) then  // Temp down key
      begin
        if SetTemp > 0
        then Dec(SetTemp);
        EEprom_Write(Mode, SetTemp);
        Delay_ms(350);
      end;

      if Button(PortA, 3, 100, 1) then  // Mode key
      begin
        Inc(Mode);
        if Mode > 1 then Mode := 0;  // Modes 0 and 1 exist
        repeat until PortA.3 = 0;    // wait for the key release

        EEprom_Write(5, Mode);
        Delay_ms(50);
      
        SetTemp := EEprom_read(Mode);
        if SetTemp > 60                // 30 degrees
        then SetTemp := 60;
      end;
    
      // make sure that, if the heater has to go on or off after a setting,
      // it does immediately
      // so, reset timebase
      if SetTemp > PrevSetTemp         // set temperature increases, goto first step
      then TimeState := 0
      else
      if SetTemp < PrevSetTemp
      then TimeState := MAXTIMESTATE;  // set temperature decreases, goto last step
    
      If SetTemp <> PrevSetTemp then
      begin
        PrevSetTemp := SetTemp;
        TimeCnt:= 0;
        TMR1H := 0;
        TMR1L := 0;
      end;
       
    end;
    
    
    // --------------- Measure Temperature ------------------- //

    // the used current temperature has a time span of 1 minute to avoid sudden changes

    if Convert then     // temperature conversion, every minute
    begin
      Convert := false;
      
      Ow_Reset(PORTA, 0);               // onewire reset signal
      Ow_Write(PORTA, 0, $CC);          // issue SKIP ROM command to DS1820
      Ow_Write(PORTA, 0, $44);          // issue CONVERT T command to DS1820
    end;

    if AcceptTemp then // once every minute
    begin
      AcceptTemp := false; // clear minute flag
      
      Cnt := 0;
      repeat                              // until read in without CRC error or after 10 retries
        Ow_Reset(PORTA, 0);               // onewire reset signal
        Ow_Write(PORTA, 0, $CC);          // issue SKIP ROM command to DS1820
        Ow_Write(PORTA, 0, $BE);          // issue READ SCRATCHPAD command to DS1820
        
        for I := 0 to 8 do
        Buff[I] := Ow_Read(PORTA, 0);     // get DS1820 result (9 bytes)
        
        Crc := CalcCrc;
        Inc(Cnt);
      until (Crc = 0) or (Cnt = 10);      // no CRC error or 10 retries passed
      
      if Crc = 0 then                     // CRC is Ok
      begin
        CurrentTemp := Buff[0];           // get DS1820 temperature

        // temperature is assumed to be always positive
        if Buff[1] > 0 then CurrentTemp := 0;       // Temp was negative
      
        DisplayTemp := true;              // measured temperature can be displayed
      end else
      
      begin // CR error, increment count in EeProm (adres  $10)
        EePromReadVariable(@CrcErrors, sizeOf(CrcErrors), $10);
        Inc(CrcErrors);
        EePromWriteVariable(@CrcErrors, sizeOf(CrcErrors), $10);
      end;

    end;
       
       
    // ----------------------- Heater Control -------------------- //

    if CurrentTemp < SetTemp then  // actual temperature too low
    begin
      Diff := SetTemp - CurrentTemp; // 1 = 0.5 degree difference, ...
      if TimeState < Diff
      then PortA.7 := 1  // heater on
      else PortA.7 := 0; // heater off
    end
    else // actual temperature OK or too high
    PortA.7 := 0;   // heater off


    // --------------- Display Status ------------------- //

    // Mode chosen
    LCD1602Goto(0,0);
    if Mode = 0
    then LCD1602Write('Dag  ')
    else LCD1602Write('Nacht');

    // "set" temperature by the thermostat
    LCD1602Goto(0,5);
    ShowTemp(SetTemp);


    if DisplayTemp then
    begin
    // actual temperature, currently used by the system
      LCD1602Goto(1,5);
      ShowTemp(CurrentTemp);
      
    // actual heater status
      Ch := ' ';
      if CurrentTemp < SetTemp then  // actual temperature too low
      begin
        Diff := SetTemp - CurrentTemp;
        case Diff of
          1,2,3: Ch := (Diff + 2)
          else Ch := 6;
        end;
      end;
      LCD1602Goto(0,15);
      LCD1602WriteChar(Ch);
      
    end;
    
    // --------------- Blinking ------------------- //
        
    if TimeCnt.0 then
    begin
      Ch := '.';
      if PortA.7 then Ch := '*';
    end
    else Ch := ' ';
    LCD1602Goto(1,15);
    LCD1602WriteChar(Ch);

   // --------------- Endless Loop ------------------- //
   
  until 0 = 1;         // endless loop
  
end.
