Active IR Beam Break Camera Trigger

Hi everyone. This is my first micro controller project. It was a fun learning experience, and so far I'm happy with the results. There is still some work to do to get it finished and deployed, but I think the electronics design and coding portion is more or less in its final form. I might resize the current limiting resistor for the IR LED to stretch the battery life, but that would probably be the biggest change. Now I need to fit it in some waterproof containers and make sure that the lens and the IR LED are positioned correctly.

As stated in the thread title, this is an active IR beam break sensor. I am using it to tell a DSLR to take a set of pictures when the beam is broken, aiming for a system I can set up in the woods for an extended period to take pictures of animals that are usually elusive. There are two halves to this, each controlled by an attiny85. The first half sends a modulated IR signal, and the second half tells the camera to take pictures if it misses an IR pulse. That's the main function, though there is also voltage monitoring of the batteries on each end and input on startup on the receiver end to set the number of pictures it takes each time and how much time should be between each picture.

I'm testing now to see how long a pair of 18650 batteries on each end will last, but I'd like to have them last a few weeks. A month or two would be great. I'm also still working on the PCB design, but that may wait until the battery needs are finalized.

Thanks for taking a look! Suggestions welcome if you see something that could be done better.

Emitter Code
Clockspeed is 1MHz
Programmed with ATTinyCore using Arduino as ISP

/*ATTiny85 Pinout

               (PCINT5/RESET#/ADC0/dW)PB5  1|      |8  VCC
Unused   PCINT3/XTAL1/CLKI/OC1B#/ADC3)PB3  2|      |7  PB2(SCK/USCK/SCL/ADC1/T0/INT0/PCINT2)             Unused
Unused   (PCINT4/XTAL2/CLKO/OC1B/ADC2)PB4  3|      |6  PB1(MISO/DO/AIN1/OC0B/OC1A/PCINT1)                ***PWM output
                                      GND  4|      |5  PB0 (MOSI/DI/SDA/AIN0/OC0A/OC1A/AREF/PCINT0)      Unused
*/

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

//Initiallize LED_pin variable and set it to digital pin 1 (PB1), which is physical pin 6
const int LED_pin = 1; 
float battVaverage = 0;
int n = 0;


void setup() {
  checkVoltageSetup();

  //Set LED_pin to output and a low state
  pinMode(LED_pin, OUTPUT);
  digitalWrite(LED_pin, LOW);

  //Set up timer for 38kHz signal in fast PWM mode
  TCCR1 = 0; // Stop Timer1
  TCNT1 = 0; // Reset Timer1 count
  GTCCR |= (1<<PSR1); // Reset prescaler

  //Turn on fast PWM mode (1<<PWM1A), enable output on OC1A pin (1<<COM1A1), 
  //and set prescaler to 1 (1<<CS10). See register description in section 12.3 of ATTiny85 datasheet
  TCCR1  = B01100001;//(1<<PWM1A)|(1<<COM1A1)|(1<<CS10);

  OCR1C = 25; //Set TOP value of counter to 25 to get a 38.4kHz signal. Follow formula on p.87 of datasheet
  OCR1A = 12; //Set approximately 50% duty cycle (half of OCR1C)
 
  //Set unused pins to input and disable pullups. See 'Configuring the Pin' on p. 54 of datasheet and http://www.gammon.com.au/power
  DDRB = (0<<DDB4) | (0<<DDB3) | (0<<DDB2) | (0<<DDB0);
  MCUCR |= (1<<PUD); 
}

void loop() {
  
  TCCR1 |= (1<<COM1A1);//Turn on PWM output
  delayMicroseconds(500); 
  TCCR1 &= ~(1<<COM1A1);//turn off PWM output
  
  n++;         //increase count up to voltage measurement

  if (n > 85713){  //checks voltage approximately every 30 minutes
    checkVoltage();
    n = 0;
    if (battVaverage < 3.15){ //Want to shut down before batter reaches 3V, adding 5% margin of error.
      shutDown();
    }
  }

  else {
  snore(20);//Power down for 20ms
  }

}

void checkVoltageSetup(){
  ADMUX = 0;                              //clear ADMUX which sets Vcc as reference voltage
  ADMUX |= (1 << MUX3) | (1 << MUX2);     // use 1.1 bandgbap voltage for input
  ADCSRA |= (1 << ADPS2);                 // set prescaler to 16 since using 1MHz clock speed
}

void checkVoltage(){
  ADCSRA |= (1 << ADEN);      // Enable ADC 
  delayMicroseconds(70);      // allow bandgap voltage to settle. P.165 ATTiny85 datasheet
  int samples = 4;
  float battVmeasure [samples];
  float battVsum = 0;
  long adcvalue = 0;

  for(int i=0; i<samples; i++){
    
    ADCSRA |= (1 << ADSC);         // start ADC measurement
    while (ADCSRA & (1 << ADSC) ); // wait till conversion complete 
    adcvalue = ADC;

    // Convert ADC value to voltage. Bandgap voltage should be between 
    // 1.0 and 1.2 and needs experimentation for each processor to get the right value
    battVmeasure[i] =  1.09 * 1024 / adcvalue; 
    battVsum += battVmeasure[i];      // Add each voltage as loop progresses
  }
  battVaverage = battVsum / samples;  // Average all the voltage measurements
  PORTB |= (1<<PORTB4);               // turn on pull up resistor for PB4
  ADCSRA &= ~(1<<ADEN);               // turn off ADC
}

void shutDown(){
  TCCR1 |= (1<<COM1A1);     //Turn on PWM output
  delay(3);                 //Send long pulse to tell receiver unit to shut down
  TCCR1 &= ~(1<<COM1A1);    //turn off PWM output


  MCUCR |= (1<<SE) | (1<<SM1) | (0<<SM0); //Enable power-down sleep mode

  \
  do {                                                                         \
    __asm__ __volatile__("sleep"                                               \
                         "\n\t" ::);                                           \
  } while (0);

}

Receiver Code
Clockspeed is 1MHz
Programmed with ATTinyCore using Arduino as ISP

/*This code waits for pulses of IR light from the other end of the camera trap trigger, the IR emitter. 
When it receives a short pulse, which is repeated once every 20ms, it wakes the device and resets the 
ATTiny85's watchdog timer. The timer is set up to overflow once ever 32ms. An overflow will wake the 
device and send a signal to the DSLR to take a set number of pictures. The number of pictures (1-9) and 
the time between them is set via buttons on startup. As long as the short pulse is received every 20ms, 
the timer should never overflow. An obstruction blocking the IR will allow the watchdog timer to 
overflow.  If a longer pulse is received, that means that the IR emitter is low on battery and will 
shut down. This triggers the receiver end to shut down as well.*/

/*ATTiny85 Pinout

                      (PCINT5/RESET#/ADC0/dW)PB5  1|      |8  VCC
Button input    PCINT3/XTAL1/CLKI/OC1B#/ADC3)PB3  2|      |7  PB2(SCK/USCK/SCL/ADC1/T0/INT0/PCINT2)             SCL
Camera          (PCINT4/XTAL2/CLKO/OC1B/ADC2)PB4  3|      |6  PB1(MISO/DO/AIN1/OC0B/OC1A/PCINT1)                PCINT
                                             GND  4|      |5  PB0 (MOSI/DI/SDA/AIN0/OC0A/OC1A/AREF/PCINT0)      SDA
*/

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


LiquidCrystal_I2C lcd(0x27, 16, 2);  //set the LCD address to 0x27 for a 16 chars and 2 line display


int shutter = 4;                 //PB4 is used to send signal to camera.
volatile int IRsensor = 1;       //B1 is used for input from sensor
volatile int n = 0;              //counter
volatile long signalStart = 0;   //time that signl from IR sensor starts
volatile int signalLength = 0;   //Length of time of signal from IR sensor
int num_photo = 1;               //number of photos to take each time the device is triggered. Set during photoTimeInput()
int WDTcycles = 0;               //number of 16ms
volatile bool tripwire = false;  //flag set by watchdog timer to trigger photo routine
float battVaverage = 0;          //average of ADC readings of VCC
volatile int oldPin1;            //value of pin 1, used to determine if it caused interrupt


void setup() {

  photoTimeInput();  //take input to set number of pictures/trigger and time between them. See below

  checkVoltageSetup();  //Set up ADC to check Vcc. Important to do after photoTimeInput as it uses ADC for button input.

  //Interrupts setup
  cli();  //Disable interrupts.

  USICR = 0;  //Disable serial communication to LCD. See p. 116 of datasheet
  USISR = 0;  //I'm not sure if all of these are needed, but the interrupts
  USIDR = 0;  //in the serial communicaiton with the LCD were causing problems
  USIBR = 0;  //with the other interrupts, and the LCD is not needed anymore.

  //Pin change interrupt setup
  DDRB |= (0 << DDB1);     //set PB1 as input
  PORTB |= (1 << PORTB1);  //enable pullup resistor
  PCMSK = 0;               //Clear PCMSK, disable all pin change interrupts
  PCMSK |= (1 << PCINT1);  //Enable pin change interrupt on PB1
  GIFR = 0;                //Reset interrupt flags
  GIMSK |= (1 << PCIE);    //Enable pin change interrupts
  sei();                   //Enable interrupts
}



void loop() {

  if (tripwire == true) {
    photo();
    tripwire = false;
  }
  if (n > 85713) {  //checks voltage approximately every 30 minutes
    checkVoltage();
    n = 0;
    if (battVaverage < 3.15) {  //Want to shut down before batter reaches 3V, adding 5% margin of error.
      watchdogOff();            //Make sure watchdog timer is disabled so the MCU stays powered down.
      shutDown();
    }
  }

  if (signalLength > 2500) {  //If the emitter is low on battery, it will send a long 3s signal to tell this receiver to power down too.
    watchdogOff();            //Make sure watchdog timer is disabled so the MCU stays powered down.
    shutDown();
  }

  watchdog32();  //Set watchdog timer to go off after 32ms
  shutDown();
  watchdogOff();  //Turn watchdog timer off when MCU wakes up
}



void watchdog32() {
  //Watchdog timer 32ms setup. See Watchdog Timer Control Register on p. 45-46 of datasheet.

  // Enable WDT change. See p. 43 of datasheet
  WDTCR |= (1 << WDE) | (1 << WDCE);

  // Enable WDT (1<<WDE), set to interrupt instead of reset (1<<WDIE), clear flag (1<<WDIF), set prescaler to 4k for 32ms timeout (1 << WP0)
  WDTCR = B11001001;
}

void watchdog16() {
  //Watchdog timer 16ms setup. See Watchdog Timer Control Register on p. 45-46 of datasheet.

  // Enable WDT change. See p. 43 of datasheet
  WDTCR |= (1 << WDE) | (1 << WDCE);

  // Enable WDT (1<<WDE), set to interrupt instead of reset (1<<WDIE), clear flag (1<<WDIF), set prescaler to 2 for 16ms timeout WPD[2:0] = 0
  WDTCR = B11001000;
}


void watchdogOff() {
  //Turn off watchdog timer

  //Enable WDT change. See p. 43 of datasheet
  WDTCR |= (1 << WDE) | (1 << WDCE);

  //Disable WDT
  WDTCR = 0;
}



ISR(PCINT0_vect) {

  int newPin1 = PINB & B00000010;  //Faster version of digitalRead(). Replacing digitalRead with this allowed the code in the ISR to run fast enough
  if (newPin1 != oldPin1) {        //Check to see if pin changed. If not, it is an interrupt from some other source and the rest of the actions will be skipped.
    oldPin1 = newPin1;
    if (newPin1 == 0) {                       //Check to see if value is LOW. This means the interrupt was caused by the falling edge at the start of the LOW signal from IR sensor.
      n++;                                    //Increase the voltage check counter
      signalStart = micros();                 //Log the time that the signal started.
    } else {                                  //If the value is HIGH, the interrupt was caused by the rising edge at the end of the LOW signal.
      signalLength = micros() - signalStart;  //Measure the length of the signal. This is used to watch for the emitter's shutdown signal.
    }
  }
}


ISR(WDT_vect) {
  tripwire = true;  //When the watchdog timer goes off, set this flag to start photo routine.
}


void photo() {
  GIMSK &= ~(1 << PCIE);  //Disable pin change interrupts to not be disrupted if signal resumes.

  for (int i = 0; i < num_photo; i++) {    //sets a loop to repeat the number of photos taken per trigger
    digitalWrite(shutter, HIGH);           //sends a signal to the shutter release to take a picture
    watchdog32();                          //Set watchdog timer for 32ms to make sure camera gets the signal
    shutDown();                            //Go to sleep
    watchdogOff();                         //Turn off timer
    digitalWrite(shutter, LOW);            //turns off signal to shutter release
    for (int x = 0; x < WDTcycles; x++) {  //Cycle through 16ms watchdog timers and wakeups to reach approximate amount input by user
      watchdog16();
      shutDown();
      watchdogOff();
    }
  }
  GIFR = 0;              //Reset interrupt flags
  GIMSK |= (1 << PCIE);  //Enable pin change interrupt on PB1
}


void shutDown() {

  //set sleep mode to power down and enable sleep mode. See p.37-38 of datasheet.
  MCUCR &= ~(1 << SM0);
  MCUCR = (1 << SE) | (1 << SM1);

  //Same as sleep_cpu() if that is included in a library

  do {
    __asm__ __volatile__("sleep"
                         "\n\t" ::);
  } while (0);

  MCUCR &= ~(1 << SE);  // disable sleep mode
}

void checkVoltageSetup() {
  ADMUX = 0;                           //clear ADMUX which sets Vcc as reference voltage
  ADMUX |= (1 << MUX3) | (1 << MUX2);  // use 1.1 bandgbap voltage for input
  ADCSRA |= (1 << ADPS2);              // set prescaler to 16 since using 1MHz clock speed
}


void checkVoltage() {
  ADCSRA |= (1 << ADEN);  // Enable ADC
  delayMicroseconds(70);  // allow bandgap voltage to settle. See p.165 of ATTiny85 datasheet
  int samples = 4;
  float battVmeasure[samples];
  float battVsum = 0;
  long adcvalue = 0;

  //Take number of measurements equal to samples variable and average them.
  for (int i = 0; i < samples; i++) {

    ADCSRA |= (1 << ADSC);  // start ADC measurement
    while (ADCSRA & (1 << ADSC))
      ;  // wait till conversion complete
    adcvalue = ADC;

    // Convert ADC value to voltage. Bandgap voltage should be between
    // 1.0 and 1.2 and needs experimentation for each processor to get the right value
    battVmeasure[i] = 1.02 * 1024 / adcvalue;
    battVsum += battVmeasure[i];  // Add each voltage as loop progresses
  }
  battVaverage = battVsum / samples;  // Average all the voltage measurements
  PORTB |= (1 << PORTB4);             // turn on pull up resistor for PB4
  ADCSRA &= ~(1 << ADEN);             // turn off ADC
}

void photoTimeInput() {
  //Take input via buttons and display on LCD

  int T_ones = 0;
  int T_tenths = 0;
  int T_hunds = 0;
  int photo_deltaT = 0;
  float buttonV = 1023;
  float lastbuttonV = 1023;
  float Vin = 1023;
  float Vb1 = 0;
  float Vb2 = Vin * 1 / 4;
  float Vb3 = Vin * 1 / 2;
  float Vb4 = Vin * 3 / 4;
  ADMUX = 0;                           //Set all bits to 0, results in Vcc being used as reference voltage
  ADMUX |= (1 << MUX1) | (1 << MUX0);  // use ADC3 for input
  ADCSRA |= (1 << ADPS2);              // set prescaler to 16 since using 1MHz clock speed

  PORTB &= ~(1 << PORTB3);  // turn off pull up resistor for PB3
  ADCSRA |= (1 << ADEN);    // Enable ADC



  int x = 1;
  int y = 1;

  while (x > 0) {

    //Request number of photos from user
    lcd.init();
    lcd.noBacklight();
    lcd.setCursor(0, 0);
    lcd.print("# of pics per");
    lcd.setCursor(0, 1);
    lcd.print("trigger?");
    lcd.setCursor(11, 1);
    lcd.print(num_photo);
    lcd.setCursor(11, 1);
    lcd.blink();

    //Take button input for photo value. Button 2 decreases, button 3 increases, button 4 selects.
    while (x == 1) {
      y = 1;
      lastbuttonV = buttonV;
      ADCSRA |= (1 << ADSC);  // start ADC measurement
      while (ADCSRA & (1 << ADSC))
        ;  // wait till conversion complete
      buttonV = ADC;
      if (lastbuttonV == 1023 && lastbuttonV != buttonV) {
        if (buttonV > Vb2 * 0.9 && buttonV < Vb2 * 1.1) {
          if (num_photo > 1) {
            num_photo = num_photo - 1;
            lcd.setCursor(11, 1);
            lcd.print(num_photo);
            lcd.setCursor(11, 1);
          }
        }
        if (buttonV > Vb3 * 0.9 && buttonV < Vb3 * 1.1) {
          if (num_photo < 9) {
            num_photo = num_photo + 1;
            lcd.setCursor(11, 1);
            lcd.print(num_photo);
            lcd.setCursor(11, 1);
          }
        }
        if (buttonV > Vb4 * 0.9 && buttonV < Vb4 * 1.1) {
          x = 2;
          if (num_photo == 1) {
            x = 0;
            y = 0;
          }
        }
      }
    }

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Time between");
    lcd.setCursor(0, 1);
    lcd.print("shots?");
    lcd.setCursor(11, 1);
    lcd.print(String(T_ones) + "." + String(T_tenths) + String(T_hunds) + "s");
    lcd.setCursor(11, 1);


    while (x == 2) {
      //Take button input for ones place of time between pictures in seconds.
      //Button 1 goes back to previous selection, button 2 decreases, button 3 increases, button 4 selects.
      while (y == 1) {
        lastbuttonV = buttonV;
        ADCSRA |= (1 << ADSC);  // start ADC measurement
        while (ADCSRA & (1 << ADSC))
          ;  // wait till conversion complete
        buttonV = ADC;
        if (lastbuttonV == 1023 && lastbuttonV != buttonV) {
          if (buttonV > Vb3 * 0.9 && buttonV < Vb3 * 1.1) {
            if (T_ones < 9) {
              T_ones = T_ones + 1;
              lcd.setCursor(11, 1);
              lcd.print(T_ones);
              lcd.setCursor(11, 1);
            }
          }
          if (buttonV > Vb2 * 0.9 && buttonV < Vb2 * 1.1) {
            if (T_ones > 0) {
              T_ones = T_ones - 1;
              lcd.setCursor(11, 1);
              lcd.print(T_ones);
              lcd.setCursor(11, 1);
            }
          }
          if (buttonV > Vb4 * 0.9 && buttonV < Vb4 * 1.1) {
            y = 2;
            lcd.setCursor(13, 1);
          }
          if (buttonV < Vb1 + 50) {
            x = 1;
            y = 0;
          }
        }
      }
      //Take button input for tenths place of time between pictures in seconds.
      //Button 1 goes back to previous selection, button 2 decreases, button 3 increases, button 4 selects.
      while (y == 2) {
        lastbuttonV = buttonV;
        ADCSRA |= (1 << ADSC);  // start ADC measurement
        while (ADCSRA & (1 << ADSC))
          ;  // wait till conversion complete
        buttonV = ADC;
        if (lastbuttonV == 1023 && lastbuttonV != buttonV) {
          if (buttonV > Vb3 * 0.9 && buttonV < Vb3 * 1.1) {
            if (T_tenths < 9) {
              T_tenths = T_tenths + 1;
              lcd.setCursor(13, 1);
              lcd.print(T_tenths);
              lcd.setCursor(13, 1);
            }
          }
          if (buttonV > Vb2 * 0.9 && buttonV < Vb2 * 1.1) {
            if (T_tenths > 0) {
              T_tenths = T_tenths - 1;
              lcd.setCursor(13, 1);
              lcd.print(T_tenths);
              lcd.setCursor(13, 1);
            }
          }
          if (buttonV > Vb4 * 0.9 && buttonV < Vb4 * 1.1) {
            y = 3;
            lcd.setCursor(14, 1);
          }
          if (buttonV < Vb1 + 50) {
            y = 1;
            lcd.setCursor(11, 1);
          }
        }
      }
      //Take button input for hundredths place of time between pictures in seconds.
      //Button 1 goes back to previous selection, button 2 decreases, button 3 increases, button 4 selects.
      while (y == 3) {
        lastbuttonV = buttonV;
        ADCSRA |= (1 << ADSC);  // start ADC measurement
        while (ADCSRA & (1 << ADSC))
          ;  // wait till conversion complete
        buttonV = ADC;
        if (lastbuttonV == 1023 && lastbuttonV != buttonV) {
          if (buttonV > Vb3 * 0.9 && buttonV < Vb3 * 1.1) {
            if (T_hunds < 9) {
              T_hunds = T_hunds + 1;
              lcd.setCursor(14, 1);
              lcd.print(T_hunds);
              lcd.setCursor(14, 1);
            }
          }
          if (buttonV > Vb2 * 0.9 && buttonV < Vb2 * 1.1) {
            if (T_hunds > 0) {
              T_hunds = T_hunds - 1;
              lcd.setCursor(14, 1);
              lcd.print(T_hunds);
              lcd.setCursor(14, 1);
            }
          }
          if (buttonV > Vb4 * 0.9 && buttonV < Vb4 * 1.1) {
            x = 0;
            y = 0;
          }
          if (buttonV < Vb1 + 50) {
            x = 2;
            y = 2;
            lcd.setCursor(13, 1);
          }
        }
      }
    }
  }
  //Display results of selection
  photo_deltaT = T_ones * 1000 + T_tenths * 100 + T_hunds * 10;
  WDTcycles = round((photo_deltaT - 32) / 16);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("# photos: ");
  lcd.print(num_photo);
  lcd.setCursor(0, 1);
  lcd.print("Delta T : ");
  lcd.print(photo_deltaT);
  lcd.print("ms");
  lcd.noBlink();
  delay(3000);
}
1 Like

I think most of us did a far more shoddy job at our first project! Congratulations!

A tiny detail is R3 on the transmitter, which isn't necessary. You're using a BJT, not a MOSFET, after all.

I never looked into measuring Vcc on the ATTiny, but you mention using the 1.1V internal reference. Does Vcc somehow scale down on the ATtiny85 when it's fed into the ADC? If not, wouldn't your voltage check always come out high?

That's fairly easy to approximate by measuring the current draw of both the TX and RX circuits. It helps that the TX LED has a very low duty cycle. The RX side probably will use next to nothing either.

Neat project, well thought-through, and props on getting to the bottom of the ATtiny85 datasheet!

Thanks for the kind words!

Ah, good catch. I had originally designed the circuit for a MOSFET, but ended up opting for the BJTs that I had on hand.

So it's an interesting technique. Technically, I'm using Vcc as the reference voltage to measure the 1.1 internal voltage. As Vcc drops, the ADC value will rise. Instead of solving for

Vin = Vref * ADC / 1024

you solve for

Vref = Vin * 1024 / ADC

It is worth noting that the datasheet says that the internal reference voltage can vary between 1.0 and 1.2 on an individual device, so there's some calibration that's needed.

True, though all I have is a pretty basic and cheap multimeter. I've assumed that it wouldn't handle the fluctuations, but it wouldn't hurt to try it out.

Nice project, especially if it's your very first one! I've never seen anyone making its first or so project with such a clean and structured loop(), handling registers and ISRs, and even a nice schematics! Congrats, good job!

Gotcha! I missed that, sorry. I get it now!

==> while(false); //0 seems to be a magic numeral

Thanks @docdoc!

Interesting. I copied that section from the sleep_cpu() macro in the tinysnore.h library. It seems to work fine. I'll have to read up on the differences.

You are a dynamic person with good writing habit and style. I would like to request you to first write down the steps that you will apply to solve your problem and then translate them into C++/Arduino codes by looking at different sources; where, ChatGPT, most of the times, provide useful tips.

Make a moderation on your sketch by replacing the register level codes as much as you can.

The while(arg); structure takes a boolean argument which is, at high level, true or false. Why 0 works for false? It is becasue the false is pre-defined symbolic name with a value of 0x00 (0 in decimal).

I could spend some time adapting this code into more common C++/Arduino commands, but I do have more productive things to do in general and more specifically for this project. As I mentioned earlier, this is my first microcontroller project. Once I found the datasheet that had instructions about how to use the various features the attiny has, setting the registers as instructed by the datasheet became my primary method. This is the first time I've spent any length of time on code since I wrote my last MATLAB program for an assignment in university about 15 years ago. I'm sure that there are more commonly used methods that would be more obvious to a more experienced person, but I am happy to have my device behaving as it should.

The project that I found online that was closest to what I wanted to do was this

That is where I found out about the attiny85. I got the idea from him of using the timers to set up the signal, but I decided to use timer1 instead of timer0 so I couldn't copy his code and not understand what it did. He didn't supply all the code for his receiving unit, and I had some slightly different behavior in mind for mine, so I haven't referenced his site for a while.

1 Like

And you have done very, very well, if I may say so. Yes, there are much quicker ways to write this, but you learned some things by doing it the detailed way that will serve you will in all subsequent projects.

But, he took help from AI, and he had used those codes without much understanding, which is evident from the presece of huge register level codes in his sketch.

The horror. How could people do such a thing??

Really, is it such a bad thing to use a tool in order to learn something? I don't see any evidence of this being an AI-generated sketch and you and I both know that trying to make ChatGPT spit out something that actually works is doomed to fail - unless you know what you're doing and can use the ideas AI throws at you and turn them into a working program.

The whole thread gives me the feeling of someone quite thoroughly understanding things in their first microcontroller project ever.

He explained that choice above. He started with the datasheet and an example sketch of someone else, and then proceeded by setting the registers on the basis of what the datasheet indicated. I'm sure you've done something similar at some point. I sure have. Not in my first project of course. I couldn't tell excrement from an implement made for digging at that point. Could you?

Since it seems to be of interest to @GolamMostafa , I saved what I got from MS Copilot along with the prompt I used. I tried this out mostly because I was curious how accurate one of these AI models could be, as I'd heard people talk about coding being a strength. I wanted to see if it was any good. Besides this, I think later I tried feeding it some code I had written to see if it could figure out where I had a problem, but it was not so helpful. That's the only AI use in the project.

The heavy presence of register editing in my code is really more of a situation where "when you only have a hammer, the whole world looks like a nail." I'm familiar with basic coding syntax, but not arduino specific commands. I found a way that worked for everything I needed to do and used it.

//This was generated by Microsoft Copilot with the following prompt. I think it does not output a 38kHz freqency, but 30.5Hz:

//Write attiny85 code using timer1 that pulses a 38khz signal on pin 6. 
//The pulse should be on for 600 microseconds and off for 900 microseconds. 
//Make note of any libraries used

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

const byte IR_LED_PIN = 6; // Pin 6 for IR LED

void setup() {
  pinMode(IR_LED_PIN, OUTPUT);
  
  // Configure Timer1 for 38 kHz signal
  TCCR1 = 0; // Stop Timer1
  TCNT1 = 0; // Reset Timer1 count
  GTCCR = _BV(PSR1); // Reset prescaler
  TCCR1 |= _BV(CTC1); // Clear Timer1 on Compare Match (CTC) mode
  TCCR1 |= _BV(CS13) | _BV(CS11); // Prescaled by 512
  OCR1C = 255; // Set TOP value for 38 kHz frequency
  TIMSK |= _BV(OCIE1A); // Enable Timer1 compare match interrupt
  sei(); // Enable global interrupts
}

void loop() {
  // Your main code (if any)
}

ISR(TIMER1_COMPA_vect) {
  digitalWrite(IR_LED_PIN, HIGH); // Turn on IR LED
  delayMicroseconds(600); // Keep it on for 600 µs
  digitalWrite(IR_LED_PIN, LOW); // Turn off IR LED
  delayMicroseconds(900); // Keep it off for 900 µs
}

Consulting ChatGPT/AI is not prohibited; but, one must know what he is doing and according;y, he has to moderate the ATtiny85 related codes and present them to comply with the syntax/semantics style of ATTInyCore being used.

One can go for register level code if that function is not found to have high level format in the ATTinyCore. For example:

OP has used:

DDRB |= (0 << DDB1);

instead of:
#define PB1 1
pinMode(PB1, INPUT);

Look at this code to chcek the end-of conversion of tha ADC.

while (ADCSRA & (1 << ADSC))
        ;  // wait till conversion complete

Theabove could be easily written as:

while(bitRead(ADCSRA, ADCS) != LOW)
{
    ; //wait
}

And what is this:?

 do {
    __asm__ __volatile__("sleep"
                         "\n\t" ::);
  } while (0);

And this one:?

MCUCR &= ~(1 << SE);  // disable sleep mode
==> bitClear(MCUCR, SE);

My reason for so much concern for NOT using the high level codes provided by ATTinyCore in order to allow the novice readers of this Forum to understand what is going in the codes of the sketch.

The OP is saying that he has something else to do which is productive -- does it mean that spending time in the Arduino Forum is unproductive? He is just happy "by managing to place an arrowhead on the correct line to make a vector".

ISR(PCINT1_vect){}
==> ISR(PCINT0_vect){}

Sorry my amateur coding is not up to your standards. I did not know about the bitRead and bitClear functions or macros or whatever is the proper term. I posted it because someone who helped with an issue in one of my previous posts asked me to and because I thought it would have been helpful for me to find when I was researching.

No, I'm saying I would rather spend my free time working on the housing for the device or one of the other projects that need doing around the house instead of rewriting my working code. I might get around to doing that one day to familiarize myself with the higher level commands, but it is not something I feel like sinking time into right now.

1 Like

Oh, and I did try to measure the current draw on the emitter end with my multimeter, but the reading was suspiciously low at 0.35mA and the IR LED would not come on while the multimeter was part of the circuit, so that reading is useless. But, after 3 days the emitter batteries are just under 4V and the receiver batteries are just over 4V, so doing pretty good still.

Someone registers on the forum, creates a couple of threads with well-documented, intelligent questions, is overall polite and constructive, and within a month finishes a nice product that's moreover documented in a complete and transparent way. Not to mention that this person actually bothers to take the time to share their results with us.

Now look at how you respond to this. Seriously, go find a mirror somewhere, and have a close look at it. That's the last I'll say about it; any reader who passes by will recognize my point anyway, so there's no need to press it further.