ATTiny, Counting with interupts

Hi Guys,

I have been messing with Teensy’s for a while now but only just discovered the joys of the ATTiny. I thought one would be perfect for a little project of mine that involves counting beeps/audio tones. However I am struggling to get it to work properly.

The basics of the circuit are a mic connected to a LM358, which is in turn connected to a LM567 tone decoder. This part is working well and picks up the tones nicely, even with my heavy rock playing in the background.

Initially I tried connecting the output of the LM567 directly to an ATTiny45, but it was difficult to to do any counting as the length of the tones/beeps can vary from a few hundred ms to several seconds. Next up I hooked up a 555 timer as a monstable and set it up to give me 1/2 second pulses, which I then feed into the ATTiny45. This works much better but I still have issues, where it does weird things, like not complete the time out loop. I am sure it’s my coding so perhaps you can take a look and tell me where I am going wrong.

#include <TinyDebugKnockBang.h>

int outPin = 3;
int onTimer = 0;
bool outputOn = false;
int beepCount = 0;
int beepTime = 0;


void setup()
{
  MCUCR |= B00000011;    //watch for rising edge
  GIMSK |= B01000000;    //enable external interrupt
  SREG |= B10000000;     //global interrupt enable
  pinMode(outPin,OUTPUT);
  digitalWrite(outPin, LOW);
  Debug.begin( 9600 );    // for tiny_debug_serial
}

void loop() {
  // if the output is on
  if (outputOn)
  {
    // check for time out
    if ( millis() - onTimer > 5000)
    {
       // if time is up turn off output, reset vars/flags
       Debug.println("Output Off!");
       digitalWrite(outPin, LOW);
       onTimer = 0;
       outputOn = false;   
       beepCount =0; 
       beepTime = 0;  
    }
  }
  else // output off
  {
    // if beepcount is non zero then a beep count is active
    if (beepCount != 0)
    {
      // 3 seconds since we started counting beeps
      if (millis() - beepTime > 3000)
      {
        Debug.println("Time Up!");
        Debug.print("Beeps: ");
        Debug.println(beepCount);
        Debug.println("********************");
        // check count
        if (beepCount >2)
        {
          // target hit, so turn on output and set timer
          Debug.println("Output On!");
          digitalWrite(outPin, HIGH);
          onTimer = millis();
          outputOn = true;
        }
        else
        {
          // reset count & flags
          Debug.println("Reset Time!");
          beepCount = 0;
          beepTime = 0;
        }
      }
    }
  }

  delay(10);
}

ISR(INT0_vect)
{
  if (!outputOn)
  {
    Debug.println("Beep heard");
    if (beepTime != 0)
    {
      Debug.println("Counting");
      beepCount +=1;
    }
    else
    {
      // if beeptime not set start the count
      Debug.println("Start count!");
      Debug.println("********************");
      beepCount = 1;
      beepTime = millis();
    }
  }
}

As you can see the code is pretty simple, but sometimes it will start the count and not activate the count timeout. The output timer seems to work fine, once triggered. I am assuming that another interrupt can’t fire while in the middle of one already, so I am not sure what the problem is.

Any suggestions on the code or a better way to achieve this would be much appreciated.

Regards,

Les

Take all the Debug statements out of your ISR and define the global variables written by the ISR to be volatile eg. volatile int beepTime = 0;

...R

Thank you so much Robin, for the quick reply.

I changed both beepTime & beepCount to volatile and so far it appears to be working perfectly. I knew it would be simple in the end, but I should have read up more on variables before hand.

EDIT: Actually it's not perfect, but it's not getting stuck counting any more. However, it does sometimes seem to not do the 3 second timeout period at all.

Regards,

Les

    if (millis() - beepTime > 3000UL)

Paul__B:     if (millis() - beepTime > 3000UL)

Thanks for the reply Paul.

We are getting there, your suggestion on its own didn't help, but I added some more debug code and saw that the beepTime was negative when it misbehaved. I have changed it to an unsigned long and it's fixed that problem, but I have more. :astonished:

Occasionally it will reset the count, when it sees multiple pulses during the time period. when this happens the beepTime is always a low value, which made my look up the millis() function, which says...

Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.

Mine is clearly not doing that, so something is resetting it somewhere along the line. The other thing that just happened, is the program got stuck in a loop printing "Output off!", which I thought should be impossible as it resets the outputOn flag in there.

Regards,

Les

Can you post your latest code?

...R

Robin2:
Can you post your latest code?

…R

Sure…

#include <TinyDebugKnockBang.h>

int outPin = 3;
unsigned long onTimer = 0;
bool outputOn = false;
volatile int beepCount = 0;
volatile unsigned long beepTime = 0;
bool printFirst = false;

void setup()
{
  MCUCR |= B00000011;    //watch for rising edge
  GIMSK |= B01000000;    //enable external interrupt
  SREG |= B10000000;     //global interrupt enable
  pinMode(outPin,OUTPUT);
  digitalWrite(outPin, LOW);
  Debug.begin( 9600 );    // for tiny_debug_serial
}

void loop() {
  // if the output is on
  if (outputOn)
  {
    // check for time out
    if ( millis() - onTimer > 5000UL)
    {
       // if time is up turn off output, reset vars/flags
       Debug.println("Output Off!");
       digitalWrite(outPin, LOW);
       onTimer = 0;
       outputOn = false;   
       beepCount = 0; 
       beepTime = 0;  
    }
  }
  else // output off
  {
    // if beepcount is non zero then a beep count is active
    if (beepCount != 0)
    {
      if (beepCount == 1 && !printFirst)
      {
        Debug.println("####################");
        printFirst = true;
      }
      // 3 seconds since we started counting beeps
      if (millis() - beepTime > 3000UL)
      {
        //noInterrupts();
        Debug.println("Time Up!");
        Debug.print("Beeps: ");
        Debug.println(beepCount,beepTime);
        Debug.print("Beeptime: ");
        Debug.print(beepTime);
        Debug.print("\tTime: ");
        Debug.println(millis());
        // check count
        if (beepCount >2)
        {
          // target hit, so turn on output and set timer
          Debug.println("Output On!");
          digitalWrite(outPin, HIGH);
          onTimer = millis();
          outputOn = true;
        }
        else
        {
          // reset count & flags
          Debug.println("Reset Time!");
          beepCount = 0;
          beepTime = 0;
        }
        Debug.println("********************");
        printFirst = false;
      }
    }
  }

  delay(10);
}

ISR(INT0_vect)
{
  if (!outputOn)
  {
    if (beepTime != 0)
    {
      beepCount +=1;
    }
    else
    {
      // if beeptime not set start the count and set time
      beepCount = 1;
      beepTime = millis();
    }
  }
}

I added the printFirst flag purely for debugging purposes, so I can see when the count starts.

Regards,

Les

Just to add to the above...

For testing I have a button hooked up to the 55 trigger and I can sit and press it 20-30 times without problems and then next press it won't recongize the pulse. The next press it works but I can see the millis() has reset, here's some sample debug code...

####################
Time Up!
Beeps: 1
Beeptime: 58744 Time: 61759
Reset Time!
********************
####################
Time Up!
Beeps: 1
Beeptime: 62599 Time: 65611
Reset Time!
********************
####################
Time Up!
Beeps: 1
Beeptime: 1736  Time: 4747
Reset Time!
********************

The resetting seems totally at random, and assuming it's not a code problem, I might remove the rest of the circuit and just hook the button up directly to the ATTiny input.

Regards,

Les

I can't grasp what the overall program is trying to do. Can you describe it in English rather than code?

...R

Robin2: I can't grasp what the overall program is trying to do. Can you describe it in English rather than code?

...R

Thanks for helping Robin.

In simple terms I am trying to count audio tones/beeps during a preset time period. As stated in my first post the tone/beep decoding is working fine. The INT0 is pulsed high when a tone is picked up. In this sample code I am looking for 3 pulses in a 3 second period, and then turn on the output for 5 seconds. When the output is on I want to ignore any other pulses until the 5 seconds are up.

Hope this makes sense.

In regards to the problems, the millis() seems to reset at random. I replaced the tone decoding circuit with a simple push button and although it's better, it still does it from time to time. It seems to happen when multiple pulse occur. Maybe there is a better way of doing the timing?

Regards,

Les

I don't have time this evening to study your code in light of your comments.

I can't imagine any reason for milis() to reset. Is it possible the whole Attiny is resetting - seems more likely. Possibly a poor connection somewhere?

Is there any risk you are running out of SRAM memory or are accidentally overwriting some memory locations.

...R

I can't imagine any reason for milis() to reset. Is it possible the whole Attiny is resetting - seems more likely. Possibly a poor connection somewhere?

I wondered that too, I'll try a different button with flying leads so i am no where near the breadboard just in case. I also wondered whether it has something to do with the debugging. I am using an Uno to program and debug the ATTiny with Coding Badly's TinyISP and simply disconnect Pin 7 from the Uno and connect the button after programming, leaving everything else connected. Everything is powered from the USB.

I might try removing the debug code and add some LED's for debugging, perhaps one to flash the count and another to indicate if millis() is reset.

Is there any risk you are running out of SRAM memory or are accidentally overwriting some memory locations.

I don't think so , that's the whole sketch posted above and this is what is reported after compile...

Sketch uses 1,834 bytes (44%) of program storage space. Maximum is 4,096 bytes.
Global variables use 138 bytes (53%) of dynamic memory, leaving 118 bytes for local variables. Maximum is 256 bytes.

Regards,

Les

I spent some more time trying to debug this today and don't really know what else to try. I tried the following... - I tried a different button well away from the circuit to make sue I wasn't knocking something while pressing it - I tried removing the debug code.

They made no difference but I thought I had solved the problem by adding some debounce code to the ISR (my hardware debounce wasn't working to well on the button). While this improves things with a button dramatically and in fact I don't think it failed at all when using the button, it still fails when I hook up the output of the rest of the circuit.

So it seems that if there is a series of pulses in very quick succession, that either the chip is resetting (I don't really know how to tell this) or the millis() counter is being reset. It's almost as if an interrupt fires while in another it resets the millis(). I know if I turn off interrupts manually it does reset the millis() counter, so maybe I just need to do the timing another way?

Hoping for some more guidance.

Regards,

Les

I hope I am not speaking too soon but I think I have finally solved this.

First of all I tried to establish whether the chip was resetting or just the millis() counter. By turning on a LED during the setup function I was able to establish that it was the chip resetting. I then removed all code bar the reset debug code and the chip still reset. During one of these test sessions I noticed that I hadn't reconnected the pulse input after flashing and yet the chip was still resetting. This told me that it must be a hardware issue, and then I realised that when I was using the button to test earlier, I had been bypassing the 555 timer. So I removed the 555 part of the circuit and since then, I haven't had a reset.

I am sure the problem is just inadequate decoupling on the 555, so I might reinstate it and test. Ideally I would like to lose the 555 part completely. I would need to modify the code somehow to be able to detect a stream of fast beeps which seem to be just counted as one though.

Regards,

Les

Glad you are making progress (and nice to see one of my suspciions proved correct).

How often do the fast beeps occur?

Post your latest code if you need further assistance.

...R

Thanks Robin.

I have reinstated the 555 with better decoupling and it still seems to be working. So hopefully that mystery is solved but as it turns out, the 555 part of the circuit is useless as it is anyway. It also only triggers once on getting a fast stream of pulses/beeps.

I don’t know how fast the beeps/pulses are as I don’t have an oscilloscope but I have tried to attach and audio file to this post to give an idea. They can sound like a constant tone at the fastest speed down to one beep at about 250ms at a guess.

EDIT: I forgot the code…

#include <TinyDebugKnockBang.h>

#define BOUNCE_DURATION 200             // bounce time of 200ms for interupt
volatile unsigned long bounceTime=0;    // variable to hold ms count to debounce a pressed switch
int outPin = 3;                         // output pin, set high when beep count meets target
int resetLEDPin = 0;                    // led lit when chips starts, just for debugging
unsigned long onTimer = 0;              // used for timing output pin 
unsigned long resetLEDTimer = 1;        // used ofr timing reset LED
volatile int beepCount = 0;             // counter for number of beeps detected
volatile unsigned long beepTime = 0;    // used ofr timing beep count period

void setup()
{
  // set registors
  MCUCR |= B00000011;                   //watch for rising edge
  //MCUCR |= B00000010;                 //watch for falling edge
  GIMSK |= B01000000;                   //enable external interrupt
  SREG |= B10000000;                    //global interrupt enable
  // set up pins
  pinMode(outPin,OUTPUT);
  pinMode(resetLEDPin, OUTPUT);
  digitalWrite(outPin, LOW);
  digitalWrite(resetLEDPin, HIGH);
  Debug.begin( 9600 );                  // for tiny_debug_serial
}

void loop()
{
  // check if reset timer is set
  if (resetLEDTimer != 0)
  {
    // if millis() not zero and 5 seconds are up turn off led
    if ( millis() !=0 && millis() - resetLEDTimer > 5000UL)
    {
      digitalWrite(resetLEDPin, LOW); 
      resetLEDTimer = 0;
    }
  }
  // if the output is on
  if (onTimer != 0)
  {
    // check for time out
    if ( millis() - onTimer > 5000UL)
    {
       // if time is up turn off output, reset vars/flags
       digitalWrite(outPin, LOW);
       onTimer = 0; 
       beepCount = 0; 
       beepTime = 0;  
    }
  }
  else // output off
  {
    // if beepcount is non zero then a beep count is active
    if (beepCount != 0)
    {
      // if 3 seconds since we started counting beeps
      if (millis() - beepTime > 3000UL)
      {
        Debug.print("Beeps: ");
        Debug.println(beepCount);
        // check count
        if (beepCount >2)
        {
          // target hit, so turn on output and set timer
          digitalWrite(outPin, HIGH);
          onTimer = millis();
        }
        else
        {
          // reset beep count & time
          beepCount = 0;
          beepTime = 0;
        }
        Debug.println("********************");
      }
    }
  }

  delay(1);
}

ISR(INT0_vect)
{
  if (!onTimer !=0 && abs(millis() - bounceTime) > BOUNCE_DURATION) 
  {
    if (beepTime != 0)
    {
      beepCount +=1;
    }
    else
    {
      // if beeptime not set start the count and set time
      beepCount = 1;
      beepTime = millis();
    }
    bounceTime = millis();            // set whatever bounce time in ms 
  }
}

Regards,

Les

beeps.mp3 (20 KB)

Sorry I'm not musical (never mind having perfect pitch). Very very crude guess at 5KHz? (Ask a musical friend).

Suppose it is 5KH that means (I think) 5 peaks every millisecond or 200 microsecs (usecs) between peaks.

I'll try and give this more thought tomorrow morning. My brain feels like sawdust right now and I'm confusing your project with another Thread.

...R

Robin2:
Sorry I’m not musical (never mind having perfect pitch). Very very crude guess at 5KHz? (Ask a musical friend).

Suppose it is 5KH that means (I think) 5 peaks every millisecond or 200 microsecs (usecs) between peaks.

I’ll try and give this more thought tomorrow morning. My brain feels like sawdust right now and I’m confusing your project with another Thread.

…R

The actual tone heard there is about 2.6Khz I think, but don’t forget it’s going through the LM567, and also the actual tone of the beep is adjustable. The decoder circuit will take care of that anyway, and all the ATTiny is interested in is counting either low pulses or the time the input pin is low.

I did get some code working last night. I basically use the interrupt to start the count and that’s all, all other interrupts are ignored while a count time period or output time period is active. During the count time period I simply read the input pin state at regular intervals (100ms in this test code), and increment the counter if it’s low. This may not be the best way of doing it, but it seems to work. The obvious potential problem is that we might miss a very fast low pulse, if we are not reading the pin at the exact moment. In practice this may not be a problem. I think the shortest pulse is likely to be about 250ms.

Anyway here’s the code so far…

#include <TinyDebugKnockBang.h>

int outPin = 3;                         // output pin, set high when beep count meets target
int inPin = 2;                          // beep pulse input pin
unsigned long onTimer = 0;              // used for timing output pin 
volatile int beepCount = 0;             // counter for number of beeps detected
volatile unsigned long beepTime = 0;    // used ofr timing beep count period
unsigned long lastBeepTime = 0;         // the time the last pin check happened
unsigned long currentTime = 0;          // stores current millis() count for comparison

void setup()
{
  // set registors
  MCUCR |= B00000010;                   //watch for falling edge
  GIMSK |= B01000000;                   //enable external interrupt
  SREG |= B10000000;                    //global interrupt enable
  // set up pins
  pinMode(outPin,OUTPUT);
  pinMode(inPin, INPUT);
  digitalWrite(outPin, LOW);
  Debug.begin( 9600 );                  // for tiny_debug_serial
}

void loop()
{
  // if the output is on
  if (onTimer != 0)
  {
    // check for time out
    if ( millis() - onTimer > 5000UL)
    {
       // if time is up turn off output, reset vars/flags
       digitalWrite(outPin, LOW);
       onTimer = 0; 
       beepCount = 0; 
       beepTime = 0;
       lastBeepTime = 0;  
    }
  }
  else // output off
  {
    // if beepcount is non zero then a beep count is active
    if (beepCount != 0)
    {
      currentTime = millis();
      // if 3 seconds since we started counting beeps
      if (currentTime - beepTime > 3000UL)
      {
        Debug.print("Beeps: ");
        Debug.println(beepCount);
        // check count
        if (beepCount >2)
        {
          // target hit, so turn on output and set timer
          digitalWrite(outPin, HIGH);
          onTimer = currentTime;
        }
        else
        {
          // reset beep count & time
          beepCount = 0;
          beepTime = 0;
          lastBeepTime = 0;
        }
        
        Debug.println("********************");
      }
      
      if (currentTime - lastBeepTime > 100)
       {
         if (!digitalRead(inPin))
         {
           beepCount +=1;
         }
         lastBeepTime = currentTime;
       }
    }
  }

  delay(1);
}

ISR(INT0_vect)
{
  if (onTimer == 0 && beepCount == 0) 
  {
     // if beeptime not set start the count and set time
     beepCount = 1;
     beepTime = millis();
     lastBeepTime = beepTime;
  }
}

I am hoping that by using the INT0 to initiate a count I can send the chip to sleep and wake it up to save power (the device will be battery powered). I also somehow need to make some things user programmable such as the sensitivity (count/time period) and output delay.

Thanks again for your help so far, and other suggestions are always welcome.

Regards,

Les

xx!!!* Just as I was about to press post everything disappeared Let's see if I can remember it.

I am still a bit unsure about your external stuff. I think your LM567 produces a LOW everytime it detects the tone it is programmed for. I also think you said that the interval between successive tones (and hence LOWs) will be at least 250msecs. Following comments are based on this, and are in no particular order.

Detecting the pulse depends as much on the width of the pulse as the interval between them. You must sample often enough to be sure to take a sample during the pulse width. My guess is that would require sampling much more frequently than every 100ms. Of course there is a risk of taking two samples in one pulse but that could be avoided by requiring a HIGH sample between relevant LOW samples.

The timing for your sampling will be a little more accurate if you use

lastBeepTime += 100;

instead of

lastBeepTime = currentTime;

For an explanation rad this long Thread

It would be better to store the 100 in a const at the top of the code.

You could stop your ISR by including detachInterrupt() within it and the using attachInterrupt() again at the appropriate place in your code.

What would happen if you allow your ISR to increment beepCount until it is 3? I don't fully understand what your project is trying to do. If it worked it would probably be the simplest solution.

...R

Robin2: I am still a bit unsure about your external stuff. I think your LM567 produces a LOW everytime it detects the tone it is programmed for. I also think you said that the interval between successive tones (and hence LOWs) will be at least 250msecs.

Correct the LM567 pulses low when it detects a tone but the 250ms is the length of a single beep, the gap between beeps can be hours or so fast that they sound like a continuous tone, as in my sample mp3 above.

As it seems the smallest individual pulse width is 250ms (note as I don't have an oscilloscope to measure so I simply recorded a beep in Audacity to measure it) then sampling at 100ms should be safe, but this could easily be shortened.

The timing for your sampling will be a little more accurate if you use

lastBeepTime += 100;

instead of

lastBeepTime = currentTime;

For an explanation rad this long Thread

It would be better to store the 100 in a const at the top of the code.

Thanks for the link. I'll read up on it.

What would happen if you allow your ISR to increment beepCount until it is 3?

When I was using the ISR to count a rapid stream of beeps, it appears as one long pulse so it only registered as one count. This is why I changed to the above code.

I don't fully understand what your project is trying to do. If it worked it would probably be the simplest solution.

In simple terms again, I just want to count the pulses during a set period or the time the pulse is low. So in theory 1 single 1 second pulse or 10 single pulses during the timing period would be treated the same in the above code.

Regards,

Les