Can't get a clean reading with PULLUP

Hey everyone!
I've been trying to create a break beam with IR-LEDs and so far I've managed to get it to work with just scouring forums and articles but now I've hit a wall.
I'm using a Nano with noname IR emitters and receivers I bought off of amazon and I've managed to to pulse them, which increased the distance but unfortunately it also made the readings "jittery". I pulsed them at 38KHz (to spec) and when I break/block the beam, I get a clean digitalRead, but when its unobstructed its fluctuating between 0 and 1. I had to put the pin for the receiving diode on pullup cause otherwise it didn't work at all, not sure if that's important..
Anyway, since I need a clean reading for the break beam I don't know what else to do. I read that most people use TSSP4038 for the receiver, so I'm wondering if a different/higher quality one would make a difference? Or should I just get a laser instead? I only need to monitor about 10cm with the beam, so I don't think that should be so hard to do??

Any suggestions would be appreciated!

#include "SoftwareSerial.h"

const int IR_LED = 3;
const int IR_RCV = 4;

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(IR_RCV, INPUT_PULLUP);
  
  TCCR2A = _BV(COM2B0) | _BV(WGM21);
  TCCR2B = _BV (CS20);
  OCR2A =  209;
  }

void loop() {
  int rcv_read = digitalRead(IR_RCV);
  if (rcv_read == 0) {
    digitalWrite(LED_BUILTIN, 0);
    Serial.println(rcv_read);
    delay(100);
    }
  if (rcv_read == 1) {
    digitalWrite(LED_BUILTIN, 1);
    Serial.println(rcv_read);
    delay(100);
    }
  }

Your loop structure is not very suitable for a modulated beam. During the unbroken time, it will be a matter of chance whether, at the instant the sensor is polled, the emitter will have been on or off.

Maybe use an interrupt instead.

What, incidentally, are you using as a receiver (part number)?

My guess is that the emitter signal is too strong - those receivers are designed for a longer distance than 10cm. The modulated light is probably bouncing off nearby objects and triggering the receiver. Try drastically reducing the emitter drive current.

Unless, you are not using that kind of receiver, but a plain diode... in which case the 38kHz is a mistake anyway...

After all is said and done, a laser might be a better choice.

Most IR receivers are designed for bursts from IR remote controls. The automatic gain control (AGC) is tuned to ignore continuous signals as 'nose'.

You need an IR receiver designed for continuous signals, like the Vishay TSSP93038 "IR Sensor Module for Reflective Sensor, Light Barrier, and Fast Proximity Applications". Your application is "light barrier".

Try this ...

const int IR_LED = 3;
const int IR_RCV = 4;

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(IR_RCV, INPUT_PULLUP);

  TCCR2A = _BV(COM2B0) | _BV(WGM21);
  TCCR2B = _BV (CS20);
  OCR2A =  209;
}

void loop() {

  byte rcv_read = 0;
  for (int i = 0; i <= 5; i++) {
    rcv_read |= digitalRead(IR_RCV) << i;
  }

  if (rcv_read < 31) {
    digitalWrite(LED_BUILTIN, 0);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
  if (rcv_read == 31) {
    digitalWrite(LED_BUILTIN, 1);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
}

Note: digitalRead will take about 5µs, 5 reads take about 25µs. Since 38kHz period = 26µs, then 5 consecutive reads will catch at least one low (0) when the beam is unobstructed.

1 Like

+1 @johnwasser #4

I can’t vouch for the part numbers, but I can say from sad experience that the cheap 38kHz module pairs do not work well or really at all with steady signals.

You can use the parts you have, probably, but in addition to providing a carrier frequency of 38 kHz, you have to modulate that carrier with, for example, a 1 kHz on/off square wave. I did this with a separate 38kHz driver that could be turnt on and off with a digital signal from the microprocessor.

So you get bursts of 38kHz at 1000 Hz, of duration well you do the math.

Then for beam breakage, you are looking to notice the presence/absence of the 1000 demodulated IR broadcast.

This has implications on how small a beam break or at what speed an object breaking a beam can be.

This was my experience. I built an elaborate cat tracking system, which worked fine except the cats both figured out how to game it and be wherever the hell they wanted to be, whilst leading my device to report them being wherever they wanted me to think I was.

YMMV.

a7

Thank you for all your answers, it's really late where I am and I've been testing things and I'll reply in detail tomorrow. Some very promising leads!

Thanks dlloyd! I tried something similar, unfortunately not as sophisticated as your approach, but the binary output is the closest to a solution I've gotten so far. It still blips occasionally though. I had to edit the code a little bit, based on the output I was getting:

  if (rcv_read < 63) {
    digitalWrite(LED_BUILTIN, 0);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
  if (rcv_read >= 63) {
    digitalWrite(LED_BUILTIN, 1);
    Serial.println(rcv_read, BIN);
    delay(100);
  }

I'm getting "111111" as the max value when the beam is broken, so I changed the decimal to 63. Every 20 - 30 seconds a "blip" of 111111 gets through, even though the beam isn't broken.. Maybe an if-function that requires a number of max readings per [n]µs? Kind of like a counter? It's a workaround, but could save me in case everything else fails. I will have to do some more testing and researching tomorrow, my coding skills aren't great.. But this is very close. Thank you!

Whoops.. I had 6 readings instead of 5.

Since you've already done some forum scouring and wall hitting (been there, done that) and you're getting a blip every 20-30 seconds, try 8 readings.

const int IR_LED = 3;
const int IR_RCV = 4;

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(IR_RCV, INPUT_PULLUP);

  TCCR2A = _BV(COM2B0) | _BV(WGM21);
  TCCR2B = _BV (CS20);
  OCR2A =  209;
}

void loop() {

  byte rcv_read = 0;
  for (int i = 0; i < 8; i++) {
    rcv_read |= digitalRead(IR_RCV) << i;
  }

  if (rcv_read < 255) {
    digitalWrite(LED_BUILTIN, 0);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
  if (rcv_read == 255) {
    digitalWrite(LED_BUILTIN, 1);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
}

True for TSOPxx receivers, but not the TSSPxx receiver OP mentioned.
Leo..

The OP mentioned them in the context of "I read that most people use TSSP4038 for the receiver," but what they have is "noname IR emitters and receivers I bought off of amazon".

Understood.

A simple diode as receiver has no band pass filter and no amplification with automatic gain control. And is much more dependent on ambient light levels and/or mains hum.
Better switch to a 3-pin receiver if you want to bridge more than 10cm.
Leo..

1 Like

Assuming that the signal is too close to the VIL threshold for LOW, you could try using a fast analog read and set the "LOW" threshold to about 3.42V like this (untested)...

const int IR_LED = 3;
const int IR_RCV = A0;

int analogReadFast(int IR_RCV) { //https://www.avdweb.nl/arduino/adc-dac/fast-10-bit-adc
  byte ADCregOriginal = ADCSRA;
  ADCSRA = (ADCSRA & B11111000) | 5; // 32 prescaler
  int adc = analogRead(IR_RCV);      // takes about 20µs
  ADCSRA = ADCregOriginal;
  return adc;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(IR_RCV, INPUT_PULLUP);  // probably should have external 10K pullup instead of this

  TCCR2A = _BV(COM2B0) | _BV(WGM21);
  TCCR2B = _BV (CS20);
  OCR2A =  209;
}

void loop() {

  int rcv_read = 0;
  for (int i = 0; i < 8; i++) {
    byte rcv_read = 0, analog2digital_read = 0;
    // Convert analog reading to HIGH (1) if signal is > 700 (about 4V), else LOW (0)
    (analogReadFast(IR_RCV) > 700) ? analog2digital_read = 1 : analog2digital_read = 0;
    rcv_read |= analog2digital_read << i; // binary record of 8 readings
    delayMicroseconds(11); // skew next reading
  }

  if (rcv_read < 255) {  // unobstructed
    digitalWrite(LED_BUILTIN, 0);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
  if (rcv_read == 255) { // obstructed
    digitalWrite(LED_BUILTIN, 1);
    Serial.println(rcv_read, BIN);
    delay(100);
  }
}

I'm still new to coding. If you know a good thread about interrupts in the context of Arduino-code, feel free to link it :slight_smile:
They're noname receivers and emitters. No part numbers anywhere.

I tried putting a resistor in between but that reduced the distance of the beam. On my cellphone camera I could see the LED being less bright.
Yes, I will look into lasers and how long I can power them with AA-batteries.

Yes, I think you are right. Right now they are actually working with a continuous signal, but I'm sure the short beam distance can be explained by this. Thank you.

Hallo Wawa, during my research I have read many of your posts, I got my HEX-code for modulating the pulsewidth from one of them (originally from Nick Gammon I think). You are very likely right, when I bought my IR-LEDs I didn't know any of this yet though :sweat_smile:. I thought a beam break would be simple enough.. anyway, I ordered some TSSP4P38s for a similar project. Thank you for all your help!

Hello dlloyd! Thank you for the code. For all intents and purposes, this solved my problem. I ordered some TSSP4P38 receivers, along with Vishay emitters, so hopefully I will get better results next time, but your solution has done the trick for now. I had to amend the code slightly, to tailor it to my use case, but turning the output to binary was a great idea!

int count = 0;

void setup() {
// ...
}

void loop() {
  byte rcv_read = 0;
  for (int i = 0; i <= 8; i++) {
    rcv_read |= digitalRead(IR_RCV) << i;
    }
  if (rcv_read < 255) {
    digitalWrite(LED_BUILTIN, 0);
    count = 0;
    }
  if (rcv_read >= 255) {
    count++;
    delay(50);
  }
  if (count == 6) {
    digitalWrite(LED_BUILTIN, 1);
    delay(1000);
    digitalWrite(LED_BUILTIN, 0);
    }
  }

I edited the code to "disregard" the rare blip when the beam breaks because of the output "jitter" by using a counter. I'm sure there is a much more sophisticated way to do this but it works. Once the beam is broken, the status-LED signals once, then turns off and nothing happens as long as the beam is broken. It resets the counter when it doesn't reach the "overrun" and once the beam is unobstructed.
Thanks again for your help! I have alot to learn :slight_smile:

Oh and I didn't try this yet cause the other code solved my problem but this looks like an interesting approach as well. I'll keep this in mind! Thank you.

Maybe interrupts are not necessary here. In your OP you referred to the receiver as a “diode” which made believe you were reading the raw 38kHz carrier signal. However, I guess from the following comments that it is actually a 3 pin IR receiver device intended for receiving encoded IR signals from, say a remote control of a television.

I have used a similar IR receiver, ie not a purpose made beam break sensor, in a beam break type application. There, the trick was, instead of sending a continuous carrier signal, to send bursts of about 10 carrier pulses (around 600 microseconds) followed by a pause. This defeats the interference suppression in the device and conforms to the “test signal” specified in the data sheet. This is similar to what @alto777 has already said.

You could also do something similar, then your logic would be something like : if a signal was detected in the last X ms, treat the beam as unbroken, otherwise treat it as broken.

Incidentally, I described the project here. Only the transmitter part is relevant because that contains the IR emitters and IR receiver used, in this case, to detect the presence of items in my mailbox:

Edit:

This thread is rich in ideas for creating a 38kHz signal also in bursts: How to create a 38 Khz pulse with arduino using timer or PWM?