//owdevice - A small 1-Wire emulator for AVR Microcontroller
//
//Copyright (C) 2012  Tobias Mueller mail (at) tobynet.de
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
// any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//
//VERSION 1.2 DS18B20  ATTINY13 (AD input) and ATTINY25 (internal sensor)

//FOR MAKE by hand
/*
avr-gcc -mmcu=attiny13 -O2 -c [name].c
avr-gcc.exe -mmcu=attiny13  [name].o -o [name].elf
avr-objcopy -O ihex  [name].elf [name].hex
*/



#include <avr/io.h>
#include <avr/interrupt.h>

#if defined(__AVR_ATtiny13A__) || defined(__AVR_ATtiny13__)
// OW_PORT Pin 6  - PB1
//Analog input PB2

//OW Pin
#define OW_PORT PORTB //1 Wire Port
#define OW_PIN PINB //1 Wire Pin as number
#define OW_PORTN (1<<PINB1)  //Pin as bit in registers
#define OW_PINN (1<<PINB1)
#define OW_DDR DDRB  //pin direction register
#define SET_LOW OW_DDR|=OW_PINN;OW_PORT&=~OW_PORTN;  //set 1-Wire line to low
#define RESET_LOW {OW_DDR&=~OW_PINN;}  //set 1-Wire pin as input
//Pin interrupt 
#define EN_OWINT {GIMSK|=(1<<INT0);GIFR|=(1<<INTF0);}  //enable interrupt 
#define DIS_OWINT  GIMSK&=~(1<<INT0);  //disable interrupt
#define SET_RISING MCUCR=(1<<ISC01)|(1<<ISC00);  //set interrupt at rising edge
#define SET_FALLING MCUCR=(1<<ISC01); //set interrupt at falling edge
#define CHK_INT_EN (GIMSK&(1<<INT0))==(1<<INT0) //test if interrupt enabled
#define PIN_INT ISR(INT0_vect)  // the interrupt service routine
//Timer Interrupt
#define EN_TIMER {TIMSK0 |= (1<<TOIE0); TIFR0|=(1<<TOV0);} //enable timer interrupt
#define DIS_TIMER TIMSK0  &= ~(1<<TOIE0); // disable timer interrupt
#define TCNT_REG TCNT0  //register of timer-counter
#define TIMER_INT ISR(TIM0_OVF_vect) //the timer interrupt service routine

//Initializations of AVR
#define INIT_AVR CLKPR=(1<<CLKPCE);\
           CLKPR=0;/*9.6Mhz*/\
           TIMSK0=0;\
           GIMSK=(1<<INT0);/*set direct GIMSK register*/\
           TCCR0B=(1<<CS00)|(1<<CS01); /*9.6mhz /64 couse 8 bit Timer interrupt every 6,666us*/

//Setup Temp Measurement DS18B20
#define INIT_TEMP   DDRB&=~(1<<PINB2); \
          ADMUX=(1<<MUX0); \
          ADCSRA= (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
#define CONV_TEMP       ADCSRA|=(1<<ADSC); \
            while ((ADCSRA&(1<<ADSC))!=0) {}\
            scratchpad[0]=ADCL;\
            scratchpad[1]=ADCH;\
            



//Times
#define OWT_MIN_RESET 51 //minimum duration of the Reset impulse

#define OWT_RESET_PRESENCE 4 //time between rising edge of reset impulse and presence impulse
#define OWT_PRESENCE 20 //duration of the presence impulse
#define OWT_READLINE 4  //duration from master low to read the state of 1-Wire line
#define OWT_LOWTIME 4 //length of low 
#endif

#ifdef __AVR_ATtiny25__ 
// OW_PORT Pin 7  - PB2


//OW Pin
#define OW_PORT PORTB //1 Wire Port
#define OW_PIN PINB //1 Wire Pin as number
#define OW_PORTN (1<<PINB2)  //Pin as bit in registers
#define OW_PINN (1<<PINB2)
#define OW_DDR DDRB  //pin direction register
#define SET_LOW OW_DDR|=OW_PINN;OW_PORT&=~OW_PORTN;  //set 1-Wire line to low
#define RESET_LOW {OW_DDR&=~OW_PINN;}  //set 1-Wire pin as input
//Pin interrupt 
#define EN_OWINT {GIMSK|=(1<<INT0);GIFR|=(1<<INTF0);}  //enable interrupt 
#define DIS_OWINT  GIMSK&=~(1<<INT0);  //disable interrupt
#define SET_RISING MCUCR=(1<<ISC01)|(1<<ISC00);  //set interrupt at rising edge
#define SET_FALLING MCUCR=(1<<ISC01); //set interrupt at falling edge
#define CHK_INT_EN (GIMSK&(1<<INT0))==(1<<INT0) //test if interrupt enabled
#define PIN_INT ISR(INT0_vect)  // the interrupt service routine
//Timer Interrupt
#define EN_TIMER {TIMSK |= (1<<TOIE0); TIFR|=(1<<TOV0);} //enable timer interrupt
#define DIS_TIMER TIMSK  &= ~(1<<TOIE0); // disable timer interrupt
#define TCNT_REG TCNT0  //register of timer-counter
#define TIMER_INT ISR(TIM0_OVF_vect) //the timer interrupt service routine


#define OWT_MIN_RESET 51
#define OWT_RESET_PRESENCE 4
#define OWT_PRESENCE 20 
#define OWT_READLINE 3 //for fast master, 4 for slow master and long lines
#define OWT_LOWTIME 3 //for fast master, 4 for slow master and long lines 

//Initializations of AVR
#define INIT_AVR CLKPR=(1<<CLKPCE); \
           CLKPR=0; /*8Mhz*/  \
           TIMSK=0; \
           GIMSK=(1<<INT0);  /*set direct GIMSK register*/ \
           TCCR0B=(1<<CS00)|(1<<CS01); /*8mhz /64 couse 8 bit Timer interrupt every 8us*/
           
           

//Setup Temp Measurement DS18B20 intern sensor
#define INIT_TEMP  ADMUX=(1<<REFS1)|(1<<MUX3)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0); \
          ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); /*ADC Freq: ~63khz*/ \
          ADCSRB=0;
#define CONV_TEMP   { uint8_t tc; int16_t sum=0; \
            for(tc=0;tc<0x10;tc++) { \
              ADCSRA|=(1<<ADSC)|(1<<ADIF);\
              while(ADCSRA&(1<<ADSC));\
              sum=sum+ADC; \
            } \
            sum=sum-0xDAF;/*calibration  0x010 are 1 K*/\
            /*sum=(sum<<4);*/  \
            scratchpad[0]=0x00ff&sum;\
            scratchpad[1]=0x00ff&(sum>>8);\
            }           
  
            


#endif // __AVR_ATtiny25__ 

#define DBGPINPORT PORTB
#define DBGPINDDR DDRB
#define DBGPIN PINB1
#define DBG_ON() do { DBGPINPORT |= (1<<DBGPIN); } while(0)
#define DBG_OFF() do { DBGPINPORT &= ~(1<<DBGPIN); } while(0)


volatile uint8_t scratchpad[9]={0x50,0x05,0x0,0x0,0x7f,0xff,0x00,0x10,0x0}; //Initial scratchpad
volatile uint8_t scrc; //CRC calculation

volatile uint8_t cbuf; //Input buffer for a command
//const uint8_t owid[8]={0xF0, 0xA2, 0xD9, 0x84, 0x00, 0x00, 0x02, 0xEA};  
const uint8_t owid[8]={0xF1, 0xA2, 0xD9, 0x84, 0xAF, 0xFE, 0x01, 0x83};
//set your own ID http://www.tm3d.de/index.php/tools/14-crc8-berechnung
volatile uint8_t bitp;  //pointer to current Byte
volatile uint8_t bytep; //pointer to current Bit

volatile uint8_t mode; //state
volatile uint8_t wmode; //if 0 next bit that send the device is  0
volatile uint8_t actbit; //current
volatile uint8_t srcount; //counter for search rom

//States / Modes
#define OWM_SLEEP 0  //Waiting for next reset pulse
#define OWM_RESET 1  //Reset pulse received 
#define OWM_PRESENCE 2  //sending presence pulse
#define OWM_READ_COMMAND 3 //read 8 bit of command
#define OWM_SEARCH_ROM 4  //SEARCH_ROM algorithms
#define OWM_MATCH_ROM 5  //test number
#define OWM_READ_SCRATCHPAD 6   
#define OWM_WRITE_SCRATCHPAD 7
#define OWM_CHK_RESET 8  //waiting of rising edge from reset pulse

//Write a bit after next falling edge from master
//its for sending a zero as soon as possible 
#define OWW_NO_WRITE 2
#define OWW_WRITE_1 1
#define OWW_WRITE_0 0


////////////////////////////////////////////
#define F_CPU 8000000UL // 8MHz
#include <util/delay.h>

#define DHTPINPORT PORTB
#define DHTPIN PORTB4
#define DHTPINDDR DDRB
#define DHTPININ PINB

// DHT22 reading code is based on adafruits DHT-sensor-library (MIT licensed)
uint8_t wait_for_level(uint8_t level) 
{
  uint8_t count=1;
  level=level<<DHTPIN;
  while ( (DHTPININ & (1<<DHTPIN)) == level ) {
    _delay_us(1);
    count++;
    if (count>250)
      // timeout
      return 0;
  }
  return count;
}

void start_conversion(void)
{
  uint8_t i;
  uint8_t do_retry=1;
  uint8_t old_error;

retry:
  old_error= scratchpad[8];
  for (i=0; i<9;i++)
    scratchpad[i]=0;
  if (!do_retry) {
    // this is the second try
    // remember error code
    scratchpad[7]=old_error;
  }

  // set to output
  DHTPINPORT |= (1 << DHTPIN);
  DHTPINDDR |= (1 << DHTPIN);
  _delay_ms(250);

  DHTPINPORT &= ~(1 << DHTPIN);
  _delay_ms(20);
  cli(); // no interrupts
  DHTPINPORT |= (1 << DHTPIN);
  _delay_us(20);
  // set to input
  DHTPINDDR &= ~(1 << DHTPIN);
  _delay_us(10);

  if (wait_for_level(0) == 0 ) {
    scratchpad[8] = 1;
    if (do_retry) {
      do_retry=0;
      goto retry;
    }
    goto end;
  };

  if (wait_for_level(1) == 0 ) {
    scratchpad[8] = 2;
    if (do_retry) {
      do_retry=0;
      goto retry;
    }
    goto end;
  };
  for (i=0; i<40; i++) {
    uint8_t dur_low;
    uint8_t dur_high;
    uint8_t bit;
    dur_high = wait_for_level(0);
    dur_low = wait_for_level(1);
    if (dur_low==0 || dur_high==0) {
      scratchpad[8]=3;
      if (do_retry) {
        do_retry=0;
        goto retry;
      }
      goto end;
    }
    if (dur_low>dur_high) {
      bit=1;
    } else {
      bit=0;
    };
    scratchpad[i/8]<<=1;
    scratchpad[i/8] |= bit;
  };
  if (scratchpad[4] != ((scratchpad[0]+scratchpad[1]+scratchpad[2]+scratchpad[3]) & 0xff)) {
    scratchpad[8]=4;
    if (do_retry) {
      do_retry=0;
      goto retry;
    }
  }

end:
  // switch off pull up
  DHTPINPORT &= ~(1 << DHTPIN);
  sei(); // enable interrupts
}



PIN_INT {
  uint8_t lwmode=wmode;  //let this variables in registers
  uint8_t lmode=mode;
  if ((lwmode==OWW_WRITE_0)) {SET_LOW;lwmode=OWW_NO_WRITE;}    //if necessary set 0-Bit 
  DIS_OWINT; //disable interrupt, only in OWM_SLEEP mode it is active
  switch (lmode) {
    case OWM_SLEEP:  
      TCNT_REG=~(OWT_MIN_RESET);
      EN_OWINT; //other edges ?
      break;
    //start of reading with falling edge from master, reading closed in timer isr
    case OWM_MATCH_ROM:  //falling edge wait for receive 
    case OWM_WRITE_SCRATCHPAD:
    case OWM_READ_COMMAND:
      TCNT_REG=~(OWT_READLINE); //wait a time for reading
      break;
    case OWM_SEARCH_ROM:   //Search algorithm waiting for receive or send
      if (srcount<2) { //this means bit or complement is writing, 
        TCNT_REG=~(OWT_LOWTIME);                    
      } else 
        TCNT_REG=~(OWT_READLINE);  //init for read answer of master 
      break;
    case OWM_READ_SCRATCHPAD:  //a bit is sending 
      TCNT_REG=~(OWT_LOWTIME);
      break;
    case OWM_CHK_RESET:  //rising edge of reset pulse
      SET_FALLING; 
      TCNT_REG=~(OWT_RESET_PRESENCE);  //waiting for sending presence pulse
      lmode=OWM_RESET;
      break;
  }
  EN_TIMER;
  mode=lmode;
  wmode=lwmode;
  
}           

TIMER_INT {
  uint8_t lwmode=wmode; //let this variables in registers
  uint8_t lmode=mode;
  uint8_t lbytep=bytep;
  uint8_t lbitp=bitp;
  uint8_t lsrcount=srcount;
  uint8_t lactbit=actbit;
  uint8_t lscrc=scrc;
  //Ask input line sate 
  uint8_t p=((OW_PIN&OW_PINN)==OW_PINN);  
  //Interrupt still active ?
  if (CHK_INT_EN) {
    //maybe reset pulse
    if (p==0) { 
      lmode=OWM_CHK_RESET;  //wait for rising edge
      SET_RISING; 
    }
    DIS_TIMER;
  } else
  switch (lmode) {
    case OWM_RESET:  //Reset pulse and time after is finished, now go in presence state
      lmode=OWM_PRESENCE;
      SET_LOW;
      TCNT_REG=~(OWT_PRESENCE);
      DIS_OWINT;  //No Pin interrupt necessary only wait for presence is done
      break;
    case OWM_PRESENCE:
      RESET_LOW;  //Presence is done now wait for a command
      lmode=OWM_READ_COMMAND;
      cbuf=0;lbitp=1;  //Command buffer have to set zero, only set bits will write in
      break;
    case OWM_READ_COMMAND:
      if (p) {  //Set bit if line high 
        cbuf|=lbitp;
      } 
      lbitp=(lbitp<<1);
      if (!lbitp) { //8-Bits read
        lbitp=1;
        switch (cbuf) {
          case 0x55:lbytep=0;lmode=OWM_MATCH_ROM;break;
          case 0xF0:  //initialize search rom
            lmode=OWM_SEARCH_ROM;
            lsrcount=0;
            lbytep=0;
            lactbit=(owid[lbytep]&lbitp)==lbitp; //set actual bit
            lwmode=lactbit;  //prepare for writing when next falling edge
            break;
          case 0x4E:
            lmode=OWM_WRITE_SCRATCHPAD;
            lbytep=2;scratchpad[2]=0;  //initialize writing position in scratch pad 
            break;
          case 0x44:  //Start Convert 
          case 0x64:  // some tool uses this command
            //CONV_TEMP;
            lmode=OWM_SLEEP;
            start_conversion();
            
            DDRB&=~(1<<PINB3);
            PORTB&=~(1<<PINB3);
            PORTB=0;
            ADMUX=(0<<ADLAR) | (1<<MUX1) | (1<<MUX0); // PB3
            ADCSRA= (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
            ADCSRB=0;
            ADCSRA|=(1<<ADSC); 
            while ((ADCSRA&(1<<ADSC))!=0) {};
            ADCSRA|=(1<<ADSC); 
            while ((ADCSRA&(1<<ADSC))!=0) {};
            scratchpad[6]=ADCL;
            scratchpad[5]=ADCH;
            
            break;
          case 0xBE:
            lmode=OWM_READ_SCRATCHPAD; //read scratch pad 
            lbytep=0;lscrc=0; //from first position
            lactbit=(lbitp&scratchpad[0])==lbitp;
            lwmode=lactbit; //prepare for send firs bit 
            break;
          default: lmode=OWM_SLEEP;  //all other commands do nothing
        }       
      }         
      break;
    case OWM_SEARCH_ROM:
      RESET_LOW;  //Set low also if nothing send (branch takes time and memory)
      lsrcount++;  //next search rom mode
      switch (lsrcount) {
        case 1:lwmode=!lactbit;  //preparation sending complement
          break;
        case 3:
          if (p!=(lactbit==1)) {  //check master bit
            lmode=OWM_SLEEP;  //not the same go sleep
          } else {
            lbitp=(lbitp<<1);  //prepare next bit
            if (lbitp==0) {
              lbitp=1;
              lbytep++;
              if (lbytep>=8) {
                lmode=OWM_SLEEP;  //all bits processed 
                break;
              }
            }               
            lsrcount=0;
            lactbit=(owid[lbytep]&lbitp)==lbitp;
            lwmode=lactbit;
          }     
          break;            
      }
      break;
    case OWM_MATCH_ROM:
      if (p==((owid[lbytep]&lbitp)==lbitp)) {  //Compare with ID Buffer
        lbitp=(lbitp<<1);
        if (!lbitp) {
          lbytep++;
          lbitp=1;
          if (lbytep>=8) {
            lmode=OWM_READ_COMMAND;  //same? get next command
            
            cbuf=0;
            break;          
          }
        } 
      } else {
        lmode=OWM_SLEEP;
      }
      break;
    case OWM_WRITE_SCRATCHPAD:
      if (p) {
        scratchpad[lbytep]|=lbitp;
      } 
      lbitp=(lbitp<<1);
      if (!lbitp) {     
        lbytep++;
        lbitp=1;
        if (lbytep==5) {
          lmode=OWM_SLEEP;
          break;
        } else scratchpad[lbytep]=0;
      }     
      break;    
    case OWM_READ_SCRATCHPAD:
      RESET_LOW;
      if ((lscrc&1)!=lactbit) lscrc=(lscrc>>1)^0x8c; else lscrc >>=1;
      lbitp=(lbitp<<1);
      if (!lbitp) {     
        lbytep++;
        lbitp=1;
        if (lbytep>=9) {
          lmode=OWM_SLEEP;
          break;            
        } else if (lbytep==8) scratchpad[8]=lscrc;
      }                 
      lactbit=(lbitp&scratchpad[lbytep])==lbitp;
      lwmode=lactbit;
      break;
    }
    if (lmode==OWM_SLEEP) {DIS_TIMER;}
    if (lmode!=OWM_PRESENCE)  { 
      TCNT_REG=~(OWT_MIN_RESET-OWT_READLINE);  //OWT_READLINE around OWT_LOWTIME
      EN_OWINT;
    }
    
    mode=lmode;
    wmode=lwmode;
    bytep=lbytep;
    bitp=lbitp;
    srcount=lsrcount;
    actbit=lactbit;
    scrc=lscrc;
}

int main(void) {
  cli();

#if 1
  DBGPINPORT &= ~(1 << DBGPIN);
  DBGPINDDR |= (1 << DBGPIN);
#endif

  mode=OWM_SLEEP;
  wmode=OWW_NO_WRITE;
  OW_DDR&=~OW_PINN;
  
  SET_FALLING;
  
  INIT_AVR
  //INIT_TEMP
  DIS_TIMER;
  
  EN_OWINT;
  sei();
  while(1){
  }
}   
