38khz Bandpass software digital filter

Hello,

I am trying to run code written by Paul O’Dowd on my UNO. Basically code is meant to detect 38khz signals using a photodiode. I dont want to use TSOP ir receiver that is why I am interested in this code.

I sucessfuly uploded code to my Arduino UNO board. I have connected IR photodiode as the author suggested. But there is no response from the board to a remote controller or my other ir transmitter. The line in processing serialgraph is flat, so i opened serial monitor changed baud to 57600, and it gives me constant zero. The board is sending over serial port, tx diode is lit but all i get is lots of zeros.

My first guess was that there is a problem witch the board, as the creator of the code used Arduino Duemilanove atmega 168 to run it. I have made some research and found that there are no differences between atmega168 and 328p that cause a problem in timer setup procedure.

Any ideas on how to make it work?

LINK to Paul O’Dowd project.

#include <avr/interrupt.h>
// Using timer0 disables millis() and delay()
// Using timer2 disables PWM analogue output.
// Not sure about timer 1.
// I think timer0 is best.
//
// Our program will work with a timer interupt service
// routine anyway, so delay shouldn't be necessary.
// You can always add an unsigned int to count and roll over,
// to take a delay from.
// Timer0 -> see around page 106 in atmega368/168 datasheet
// 
// The important parts for the timer are the setup() and ISR() 
// code.
//
// The interupt routine reads the analog A0 pin at 100khz.
// Read the comments through the code!
// Once an input buffer is full, the filter is run to 
// produce a variable analog output of signal strength.
// Then the process loops.
//
// Once you are happy, remove the Serial port commands for
// best performance.
//
// The filter is a recursive band-pass described in this
// incredibly awesome and free book:
// http://www.dspguide.com/ch19/1.htm

// Definitions.
const int BUFFER_SIZE = 45;    // use an odd number

// Global variables.
double buf[BUFFER_SIZE];  // Analog readings at 100khz & stored here
double out[BUFFER_SIZE];  // output of filter stored here.
int buffer_index;         // Interupt increments buffer
boolean buffer_full;      // Flag for when complete.

double a0,a1,a2,b1,b2; // filter kernel poles
double f,bw;           // frequency cutoff and bandwidth
double r,k;            // filter coefficients


void setup() {
   
   // Clear global variables before the timer
   // is activated.
   for( int i = 0; i < BUFFER_SIZE; i++ ) {
      buf[i] = 0;
      out[i] = 0; 
   }
   buffer_index = 0;
   buffer_full = false;
   
   // Set our anolgue input and use 
   // internal pull up resistor.
   pinMode(A0, INPUT);
   digitalWrite(A0, HIGH);
 
   // Configure the trigger and frequency of our timer.
   cli(); // disable interupts.
   
   // Clear the control registers so we can OR in bits safely
   TCCR0A = 0; // initialise control register to 0
   TCCR0B = 0; // initialise control register to 0
   TCNT0 = 0;  // initialise the counter to 0
                               
   // We set no prescaler, meaning the interupt is clocked at 16mhz  
   TCCR0B |= (1 << CS00); 
   
   // Set the max compare value so we achieve 100khz, 
   OCR0A = 159; // (16000000) / (100000) -1   max value is 256
 
   // OR in a bit to WGM01, setting it as CTC  
   // CTC = Clear Timer on Compare match
   TCCR0A |= ( 1 << WGM01); 
                            
   // enable timer compare interrupt
   TIMSK0 |= (1 << OCIE0A);
   
   // Activate!!
   sei();
  
   // Lets sort out the filter variables before we end setup.
   
   // Cut-off frequency.
   // We are looking for a 38khz IR TV remote.
   // F is fraction of the sample frequency 
   // It has to be between 0 and 0.5.  Therefore, the interupt
   // needs to be at least *double* the bandpass frequency.
   // I picked 100khz as a nice number to scale from.
   // So, f = (100khz * 0.38) = 38Khz
   f = 0.38;  
   
   // Bandwidth (allowance) of bandpass filter.
   // Same principle as above (fraction of 100khz).
   // We are using this filter to get rid of ambient environment
   // noise.  20khz seems like a big band, but I wouldn't expect 
   // there to be much in the khz.  You can fine tune downwards.  
   bw = 0.2;  
   
   // Maths. Read the book.  Does the trick.
   r = 1 - ( 3 * bw ); 
   k = 1 - ( 2 * r * cos(2 * PI * f ) ) + ( r * r );
   k = k / (2 - ( 2 * cos( 2 * PI * f ) ) );
   
   a0 = 1 - k;
   a1 = (2 * ( k -r ) ) * ( cos( 2 * PI * f ) );
   a2 = ( r * r ) - k;
   b1 = 2 * r * cos( 2 * PI * f );
   b2 = 0 - ( r * r ); 
   
   
   // Activate serial port.
   Serial.begin( 57600 );
      
   Serial.println("Reset");

   
   
 }

void loop() {
  
  float output;
  
  // We only do something once the buffer is full.
  if( buffer_full == true ) {
    
    // Run the input buffer through the filter
    output = doFilter();
    
    // We are going to transmit as an integer
    // Move up the decimal place
    output *= 1000;
    Serial.println( (int)output );

    // Reset our buffer and interupt routine
    buffer_index = 0;  
    buffer_full = false;
  }
  
}

// This filter looks at the previous elements in the 
// input stream and output stream to compound a pre-set
// amplification.  The amplification is set by a0,a1,a2,
// b1,b2.  Please see the linked book, above.
double doFilter() {
   int i;
   double sum;
   
   // Convolute the input buffer with the filter kernel
   // We work from 2 because we read back by 2 elements.
   // out[0] and out[1] are never set, so we clear them.
   out[0] = out[1] = 0;

   for( i = 2; i < BUFFER_SIZE; i++ ) {
      out[i] = a0 * buf[i];
      out[i] += a1 * buf[i-1];
      out[i] += a2 * buf[i-2];
      out[i] += b1 * out[i-1];
      out[i] += b2 * out[i-2]; 
   }
   
   // Bring all the output values above zero
   // To get a well reinforced average reading.
   for( i = 2; i < BUFFER_SIZE; i++ ) {
    if( out[i] < 0 ) out[i] *= -1;
    sum += out[i]; 
   }
   sum /= BUFFER_SIZE -2;
   
   return sum; 
}

ISR(TIMER0_COMPA_vect) { // called by timer0 automatically evey 100khz
    // Fills our input buffer from analog pin A0 until full and reset
    if( buffer_index >= BUFFER_SIZE ) {
       buffer_index = 0;
       buffer_full = true; 
    } else if ( buffer_full == false ){
       buf[ buffer_index ] = (double)analogRead(A0);
       buffer_index++;  
    }
}

You can't read ADC at 100 kHz, w/o mod. registers. Blog-post doesn't make any sense to me. It 'd be possible by setting ADC prescaller to 2 Mhz get ~ 6 usec conversion time, but you can't use "analogRead" in your code.

It looks like the author managed to run this code successfully. You think is fake?

I'm sure, that author is misunderstanding what he is doing. Default arduino ADC conversion time is 120 usec, it makes 9.6 kHz sampling rate at maximum. Search on this forum, or Google, there are many resources that explain how set higher sampling by arduino. It also 'd be possible to do "under sampling" , Shannon theorem allows rate 2xBandwidth, and in case narrow band (38 kHz IR) nothing prevent to set ADC rate to 10 or 9 kHz, but author didn't mention anything on his page about this technics.

Thank you for valuable information. I am new to Arduino and AVR programming, the code for bandpass filter is bit complicated for me.

I made some research and i added three new lines to the oryginal bandpass filter code to achieve ADC prescalle at 2Mhz.

const unsigned char PS_8 = (1 << ADPS1) | (1 << ADPS0);

void setup() {

ADCSRA &= ~PS_128;
ADCSRA |= PS_8;

I uploaded program to board, but nothing changed. Is there more to add or change in the code to make it run?

Beside unknown sampling rate, this part

// Bring all the output values above zero
   // To get a well reinforced average reading.
   for( i = 2; i < BUFFER_SIZE; i++ ) {
    if( out[i] < 0 ) out[i] *= -1;
    sum += out[i]; 
   }
   sum /= BUFFER_SIZE -2;

gives me impression of a junk-code. I’d not spend any minute trying to debug it.
What is your objective?

Since the high half of each symbol (bit) of the RC-5 code word contains 32 carrier pulses, the symbol period is 64 x 27.778 μs = 1.778 ms, and the 14 symbols (bits) of a complete RC-5 code word takes 24.889 ms to transmit.

You definitely need more than 45 samples, at 100 ksps 24.889 ms translates to 2489 samples, or you don’t need to decode?

Sampling rate can be calculated using information provided on this site :

http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/

But decoding is not my concern at this point. I want to be able to detect 38khz frequency using a 640nm red laser and a visible light photodiode equipped with 640nm optical filter. I dont want to use infrared light source.

So the goal is to make bandpass filter work so I can experiment with laser light detection under variable weather conditions and light sources.

Here is hardware description from your source:

You will need a simple passive Infra-red photodiode, they look like LED's. Plug the cathode into A0, and the anode into ground, and the code uses an internal pull up resistor in the arduino.

If you have same hardware set-up, than start your research again. Because author is a victim of a bad pharmacologist, why someone would want to detect IR beam a few cm. from the source? Suggestions: 1. Find good transconductance amplifier, circuits specifically design to amplify low current photodiode signal. 2. Run arduino example "analog out serial" - get noticeable difference in digits with an w/o light source; 3 What is your source? Post a drawings, how you generate 38 kHZ, index of modulation? Have you other measurement equipment, that could confirm your light is perfectly modulated with carrier? Digital filtering is a last step on a long road of a project.

I already used this schematic for amplification couple of days ago. Removed digitalWrite(A0, HIGH); entry in code to not use built in pullup resistor.

As for the transmitter. My first transmitter was based on NE555 circuit working in astable mode. I unfortunately dont own neither have access to oscyloscope. And to achive perfect frequency manually by calculating resistors value in the circuit gave me only “I guess it should work” assumption. My basic remote test circuit confirmed it worked around 38khz. But thats irrelevant now.

I found a simpler way to make stable transmitter. I simply used IRremote library uploaded IRsendDemo to my Arduino mini pro.

/*
 * IRremote: IRsendDemo - demonstrates sending IR codes with IRsend
 * An IR LED must be connected to Arduino PWM pin 3.
 * Version 0.1 July, 2009
 * Copyright 2009 Ken Shirriff
 * http://arcfn.com
 */

#include <IRremote.h>

IRsend irsend;

void setup()
{
  Serial.begin(9600);
}

void loop() {
  if (Serial.read() != -1) {
    for (int i = 0; i < 3; i++) {
      irsend.sendSony(0xa90, 12); // Sony TV power code
      delay(40);
    }
  }
}

After that I built simple circuit to drive a laser diode using transistor connected to pin 3 with resistor, and external power supply to achieve safe 150mA current on laser diode. I Made a test with IR diode TSAL6100 (can hold up to 200mA current) to confirm that circuit is working correctly and i managed to turn my tv on and off.

I am lost now and not sure how to achieve my goal, thought Paul O’Dowd code will work and I can jump straight to testing.

thought Paul O'Dowd code will work

A very high percentage of material posted on the web is junk.

I gave it a thought and Magician pointed me in the right direction and im thankful for that.

This code may be junk but there is some valuable information in it, I am talking about Recursive Filters equation. So I think the code should be written from a scratch and The Recursive Method should be possible to implement.

For this moment I will stick to Magician suggestions

  1. Find good transconductance amplifier, circuits specifically design to amplify low current photodiode signal.
  2. Run arduino example "analog out serial" - get noticeable difference in digits with an w/o light source;

I have a transmiter, now I need good receiver. After that I can go deeper into coding to build something more complex.

Did you notice the comment on Paul O'Dowd's web page questioning whether the programmed filter was even a bandpass filter? The user states that it is a high pass filter, which won't do you any good.

You might do some reading, or your own tests to see what that code actually does. It is simple to make up some input data for testing.

I will not bother with Paul code anymore. I decided to focus on getting accurate readings from photodiode for now, then I will decide how to translate results.

  1. I searched internet and found external ADC IC like this one MCP3304 There is code for operation with Arduino on Arduino playground. It has better resolution than Arduino, It is a 13 bit IC so it will give more accurate results.

A normal conversion in the ADC takes 13 ADC clock cycles. The first conversion takes 25 clock cycles. So our ADC clock speed needs to be factored down by 13 to figure out how many samples we will be able to achieve per second.

Above quote concerns Arduino ADC. Does the same rule apply to MCP3304? What do you think, will it be good for 38khz measurement?

  1. Now for the receiver part.

Do I need to connect Vcc of the transimpedance amplifier to +5Volts and GND pin to ground for it to operate? or it converts current to volts without power supply? If so, should i use second opAmp to amplify voltage from transimpedance amplifier?

All amplifiers require a power supply. The transimpedance amplifier shown requires a bipolar power supply, preferably at least +/- 9V.

That particular circuit won't work with Arduino, or a standard external ADC as it provides a negative output voltage proportional to the photodiode current.

Op amp circuits that run on a unipolar power supply, e.g. 5V, are best if the chosen amp is "rail-to-rail" for both input and output (as far as possible).

Does the same rule apply to MCP3304?

Always check the device data sheet for the specifications.

What do you think, will it be good for 38khz measurement?

The device data sheet states 100 kilosamples/second.

OP: you still didn't say how far you need IR detector to work. Hardware discussions w/o specification is a non-sense. You may be fine with just a photodiode/ phototransistor for a few cm distance, hands detection for example. On a long range, you may need 60 MSPS or even more. I'm not saying liquid nitrogen.... Recently, I was designing IR laser rangefinder myself, and failed to meet tough budget/ arduino technical limitation. So I come to conclusion, that all project is non practical , better to buy already existing device on a market.

Lets say for now that I want to place receiver at distance of 50 meters from transmitter. Consider it as free space communication. It won't work the way rangefinder work because beam doesn't need to reflect back to the transmitter location.

Like this?

Not really, i was wrong writing free space communication, I dont want to transfer data. The idea is more like laser remote controller, after receiving RC code from laser Arduino performs specific action coded on the receiver side. I know that there are simpler ways to do this, like using TSOP receiver and IR LED with collimating lens. But I want to do this the hard way(fun way).

You mentionded that you where designing IR laser rangefinder, maby you have transpendence amp circuit that i can use?

The reason that I asked about conversion rate in ADC is that I am a little bit confused. MCP3304 datasheet states 100 kilosamples/second and 13 CLK periods to convert. So I am not sure if I should devide Ksps per 13, datasheett dont mention anything about speed of IC.

My circuits was intended for higher freq., 500 kHz or so. Idea was to tune L and C (photodiode) making it selective to specific band. It won’t work on 38 kHz, you need 100x bigger inductor -);.
So, if you will use a laser directly pointed to receiver, I don’t think you need amplifier, instructables project is correct, I think.

My understanding of MCP3304 , that you are setting conversion rate by applying desired freq.to CLK pin, according to picture 6.2 you need 100 x 21 = 2.1 MHz clock to get 100 ksps.

Ok i was working lately on Arduino code capable of doing accurate sensor readings. The goal is to store 13bit value in unsigned long variable and assign time value (when reading occurred) to that variable.

I connected MCP3304 to my Arduino board to get better resolution. I used this library for SPI communication. I only changed divider from DIV16 to DIV8 to get better speed.

at DIV16, 512 in 22ms (23.3 ksps)
at DIV8, 512 in 16ms (32 ksps)

Next I used this library to get better clock precision because:

micros()

Description

Returns the number of microseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 70 minutes. On 16 MHz Arduino boards (e.g. Duemilanove and Nano), this function has a resolution of four microseconds (i.e. the value returned is always a multiple of four).

In theory that library gives me 1us precision for readings.

Here is my code:

#include <eRCaGuy_Timer2_Counter.h>
#include <MCP3304.h>
#include <SPI.h>

MCP3304 adc1(10);   //creat an instance with pin 10 as CS

unsigned long time[10];      // storing micros
int adc[10];                 // storing mcp readings

void setup(){
  
  timer2.setup(); //this MUST be done before the other Timer2_Counter functions work. 
  Serial.begin(115200);
}
void loop(){
  
  unsigned int i;
  
  for(i=0;i<10;i++){
    time[i] = timer2.get_count();  // <eRCaGuy_Timer2_Counter.h> function for micros precision
    adc[i] = adc1.readSgl(1);   // <MCP3304.h> function for reading values
  }
  
  for(i=0;i<10;i++){
    Serial.print(time[i]);   // prints micros
    Serial.print("----->>   ");
    Serial.print(adc[i]);    // prints MCP3304 readings
    Serial.print("\n");
  } 
  delay(100000);
}

And results from serial monitor.

Time ADC readings

362 ----->> 3733
467 ----->> 3754
581 ----->> 3735
686 ----->> 3733
799 ----->> 3733
905 ----->> 3730
1010 ----->> 3733
1124 ----->> 3754
1229 ----->> 3735
1343 ----->> 3752

Is it possible to sample faster? There is a 105 us gap between readings. At DIV8 it should be 16us plus time for (i=0;i<10;i++) to complete, which I tested before and it equals 8us. What am I missing? Why 105us instead of 32us ?