based on the concept of user1843591 answer, i researched some more and found the example of Nick Gammon in the arduino forum.
after some tweaks i came up with the following:
(based on both)
it uses Timer1 to generate a 38kHz signal and
Timer2 to generate a 760Hz cycle that switches the 38kHz signal on and off.
this way the recommend pulse-cycles and gap times for the Receiver (for me TSOP4438) is preserve.
it also implements global flags to decouple the ISRs & mainloop.
(both files should be in a folder IR_TSOP4438_light_barrier
)
IR_TSOP4438_light_barrier.ino
// simple light barrier test
extern volatile bool light_barrier_broken;
void setup(){
Serial.begin(115200);
Serial.println("IR_TSOP4438_light_barrier");
Serial.println("setup...");
setupIRLightBarrier();
Serial.println("running.");
}
void loop() {
if(light_barrier_broken) {
Serial.println("light barrier 1 break detected");
delay(20);
digitalWrite (LED_BUILTIN, LOW);
light_barrier_broken = false;
}
}
ir_light_barrier.ino
// based on
// Example of modulating a 38 KHz carrier frequency at 500 Hz with a variable duty cycle
// Author: Nick Gammon
// Date: 24 September 2012
// https://forum.arduino.cc/t/how-to-create-a-38-khz-pulse-with-arduino-using-timer-or-pwm/100217/44
// tweaked for TSOP4438
// https://www.vishay.com/docs/82459/tsop48.pdf
// find max burst length and min gap times:
//
// Minimum burst length 10 cycles/burst
// After each burst of length 10 to 40 cycles
// a minimum gap time is required of ≥ 10 cycles
// Maximum number of continuous short bursts/second: 1500
//
// translates to:
// 38kHz = 26,3 us / Pulse → *10= 263us
// max burst length: <= 1052us (26,3*40)
// min gap length: >= 263us (26,3*10)
// total cycle time of (1052+263=) 1315us
// that violates the max bursts/second (666us/burst)
//
// on option is to optimize for the most bursts/second:
// so we use the min gap time as given and use the rest of the available time.
// 666us - 263us = 403 us burst length
// hopefully this way the AGC does not filter our stream...
// we now have to fit this to the best available prescaler / counter values:
// 672us fits good (this way we have less than the 1500 burst)
// this translates to 42 counts a' (0,0625us*256=) 16us
// so we use a gap length of 16us*17 = 272us
// this gives us a burst length of 16us*(42-17)= 400us
//
// second option is to optimize for the longest burst length and have less bursts/second.
// here we will use a in-between leaning towards longer bursts:
// 0,0625us*256=16us
// 16us*50counts = 800us = 0,80ms = 1250,00Hz = 1250bursts/s
// 16us*80counts = 1280us = 1,28ms = 781,25Hz = 781,25bursts/s
// gap length: 16us*17 = 272us
// burst length: 16us*(80-17) = 1008us
// http://www.gammon.com.au/forum/?id=11504
// Timer 1
// OC1A: D9
// OC1B: D10
const byte LED = 9;
// Timer 2 (8bit)
// OC2A: D11
// OC2B: D3
// https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
const byte light_barrier_PIN = 2;
// const byte light_barrier_PIN = 3;
// global flags to be handled in the main loop...
volatile bool light_barrier_broken = false;
// Clock frequency divided by 38 kHz frequency desired
const long timer1_OCR1A_Setting = F_CPU / 38000L;
// (16000000 / 38000) = 421,05
// this only works on Timer 1 - as it is a 16bit timer.
// // ------------------------------------------
// // this is about the slowest possible setting.
// // timer2 target counts:
// // CPU 16MHz (0,0625us)
// // target 63Hz (16000us = 16ms)
// // prescaler 1024
// const long timer2_top = F_CPU / 63L / 1024;
// // (16000000 / 63 / 1024) = 250
// // ------------------------------------------
// // optimized for ~1500 bursts /second
// // target counts:
// // CPU 16MHz (0,0625us)
// // prescaler 256
// // target 1,488kHz (672us = 0,672ms)
// const long timer2_top = (F_CPU / 256L) / 1488L;
// // (16000000 / 256) / 1488 = 42
// // calculate on / off ratio (toggle point)
// const long timer2_compare = timer2_top * 400L / 672L;
// // 42 * 400 / 672 = 42 - 17 = 25
// ------------------------------------------
// in-between - leaning for longer bursts
// target counts:
// CPU 16MHz (0,0625us)
// prescaler 256
// target 781kHz (1280us = 1,28ms)
const long timer2_top = (F_CPU / 256L) / 781L;
// (16000000 / 256) / 781 = 80
// calculate on / off ratio (toggle point)
const long timer2_compare = timer2_top * 1008L / 1280L;
// 80 * 1008 / 1280 = 80 - 17 = 63
volatile bool sender_active = false;
void light_barrier_ISR() {
if (
sender_active
&& digitalRead(light_barrier_PIN) == HIGH
) {
digitalWrite (LED_BUILTIN, HIGH);
light_barrier_broken = true;
// if() {
// }
} else {
digitalWrite (LED_BUILTIN, LOW);
}
}
ISR (TIMER2_COMPA_vect) {
// used to combine the two timers...
// toggle D13
// PINB = bit (5);
// digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)) ;
if (sender_active == false) {
// enable timer1 output
TCCR1A |= bit(COM1A0) ; // Toggle OC1A on Compare Match
// digitalWrite (LED_BUILTIN, HIGH);
delayMicroseconds(100);
sender_active = true;
} else {
sender_active = false;
delayMicroseconds(100);
// disable IR-Receiver Input check
//detachInterrupt(digitalPinToInterrupt(light_barrier_PIN));
// disable timer1 output
TCCR1A &= ~bit(COM1A0) ; // DO NOT Toggle OC1A on Compare Match
digitalWrite (LED, LOW); // ensure off
// digitalWrite (LED_BUILTIN, LOW);
}
}
void setupIRLightBarrier() {
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// set up Timer 1 - gives us 38.095 KHz
TCCR1A = bit (COM1A0); // toggle OC1A on compare
TCCR1B = _BV(WGM12) | _BV (CS10); // CTC to OCR1A, No prescaler
OCR1A = (16000000L / 38000L / 2) - 1; // zero relative
// setup Timer 2
TCCR2A = 0;
TCCR2B = 0;
// toggle OC2A on compare
// TCCR2A |= bit(COM2A0);
// fast pwm to OCR2A
TCCR2A |= bit(WGM21) | bit(WGM20);
TCCR2B |= bit(WGM22);
// prescaler 1024
// TCCR2B |= bit(CS22) | bit(CS21) | bit(CS20);
// prescaler 265
TCCR2B |= bit(CS22) | bit(CS21);
// top
OCR2A = timer2_top - 1; // zero relative
// switch point
OCR2B = timer2_compare - 1; // zero relative
// enable interrupts
TIMSK2 = bit(OCIE2A);
// TIMSK2 = bit(OCIE2B) | bit(OCIE2A);
// enable IR-Receiver Input check
attachInterrupt(
digitalPinToInterrupt(light_barrier_PIN),
light_barrier_ISR,
CHANGE
);
}