Trouble with Input Capture (again)

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

uC = ATmega16
clk = 1MHz internally clocked
src = Dirt cheap MP3 player

It's the weekend so I have time to work on this project and once again I feel like I am still arguing with the same wall. The forum got be on the right track last weekend in this thread (https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=56730&start=all&postdays=0&postorder=asc).

Since then, I have created a circuit/program that responded when the ICP was brought from 5V to GND (with a lot of switch bouncing but it was a test so who cares). I have also switched from a mega168 to a mega16 so I could have more I/O pins available while I was programming/debugging.

I believe I have this narrowed down to a problem with my code, I am incorrectly assuming the interrupts can fire as quickly as I have coded for, or my signal is not strong enough. I have no way of checking the signal because I am scopeless. I am seriously considering selling a kidney to get a scope but I probably won't get it in time for this project.

Is there anything blatantly wrong with my input capture setting or my interrupt handling that would prevent this from working? If the code is good, I'll move this over to the Electronics Forum.

Thanks.

#include 
#include 

void ioinit (void);

unsigned int tcnt_temp;

int main (void)
{
    ioinit (); 

    TCCR1B |= (1 << ICES1); // Input Capture Edge Select
                            // 0 == Falling / 1 == Rising
    // TCCR1B |= (1 << ICNC1); // Noise canceling

    TIMSK |= (1 << TICIE1); // Enable Input Capture Inerrupts.

    sei (); // Turn on global interrupts.

    for (;;)
    {   
        // Watch the daisies grow.
    }   
}

void ioinit (void)
{
    DDRA = 0xff; // All optouts
    DDRB = 0x1f; // PB5-PB7 ISP
    DDRC = 0xff; // All outputs
    DDRD = 0x00; // Not outputs
}

/*
 * Turn timer off.
 *
 * If the timer overflows, the control tone has stop. Therefore,
 * the timer is reset for the next control tone by turning off
 * and resetting the counter back to zero.
 */

ISR (TIMER1_OVF_vect)
{
    TCCR1B |= (0 << CS10);
    TCNT1 = 0;
}
/*
 * Start timer and measure the time between interrupts.
 *
 * Check Timer Control Register B to see if the timer is on. If
 * the timer is off, turn it on and return from the interrupt.
 *
 * The second time through will have the time since the first
 * interrupt fired. An integer devide will take care of any slop
 * in the timing and determine which case to run from the switch
 * statement. The appropriate pin is toggled and the counter is
 * set to a non-activating place holder value.
 *
 * Any subsuquent time the interrupt fires will reset the
 * counter value back to the place holder value. This prevents
 * the output from toggleing back and forth for the 0.5 to 1.0
 * second the control tone is active.
 */

ISR (TIMER1_CAPT_vect)
{
    tcnt_temp = TCNT1; // Read and store the counter asap

    if (TCCR1B & CS10)
    {
        TCCR1B |= (1 << CS10); // Start timer
        return; // Return from interrupt
    }

    if (tcnt_temp > 5000)
    {
        tcnt_temp = 5000;
        return; // Return from interrupt
    }

    switch (tcnt_temp / 100)
    {
        case 1:      // Day01 PA0  150us 6667Hz
            PORTC ^= (1 << 1);
        break;

        case 2:      // Day02 PA1  250us  4000Hz
            PORTC ^= (1 << 2);
        break;

        // There are a lot more cases but they all do the same thing.

    }

    tcnt_temp = 5000;
}

You can have my mac when you pry my cold dead fingers off of it.

Kevin McEnhill -- mcenhillk@gmail.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you are talking low frequncy audio (anything you to about 15KHz) then a (free) "soundcard scope" might give you a view on what's happening. See the sticky freeware thread in Off topic where Nard presented a suitable input divider/protection circuit to bring the AVR TTL levels down to those expected at the soundcard's line input.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

if (TCCR1B & CS10)
    {
        TCCR1B |= (1 << CS10); // Start timer
        return; // Return from interrupt
    }


Unless I'm missing something, should this be "if ( !(TCCR1B & CS10) )" to check if the timer is not set?

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, if you're using input capture, should you be reading the ICR1 register instead of TCNT1, as it should hold the timer value when the edge detect occurred. Not that it'd be much different, I guess.

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Reading this a bit more, I'm either way off or you need to redesign the interrupt handler a bit. What you're doing now is always reading the current value of the timer when you execute the loop. That means you never have something previous to compare it to.

What you should do is when the interrupt triggers, read the value of ICR1 into a temporary variable somewhere and set a flag saying that a frequency capture is in progress.

Then the next time it triggers, check that flag. If it's not set, do as above. If it is set, then subtract the value in the temp variable from the new value in ICR1. This should give you the difference between the two, and thus the time that has expired. Then clear the flag, and process the difference in time and use that to calculate the frequency.

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I went back to the datasheet and there really isn't a difference between ICR1 and TCNT1. ICR1 gets a copy of TCNT1 when a capture even happens. Because I start the timer on the first round and read the results on the second time through, both values will produce the same time delay. Of course, using TCNT1 for an input capture event is kind of like drinking beer with your pinky extended. I'll make that change.

Stupid logic! ARRRGGG!

AH! It took the fourth time through to figure out what you were talking about. I have only been writing code for 10 years (not this low level though) and I still tend to read what I meant and not what I said.

What I have done instead of setting a flag is to just use the raw ICR1 value in the switch statement. By the time I get there, I know the timer is running and the value is below the flag value I set after the switch statement (i.e. 5000). My requirements are not to measure the exact frequency but to make sure it is within a fairly wide range. The integer divide takes care of that slop.

The more I play with this, the more I think I am not picking up a strong enough signal to trigger the input capture. Does anyone know that the amplitude comes out of a headphone jack?

You can have my mac when you pry my cold dead fingers off of it.

Kevin McEnhill -- mcenhillk@gmail.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't see the timer start value set to 0. So you start at some more or less random value. Appart from this your code could actually give usefull, but not very accurate results.
There is a differenz between the ICP and TCNT1 register (why should there be 2 regs then). Using the TCNT1 register is possible, but this ignores the ICP funktion and uses the ICP inputs as a kind of external Interrupt. The ICP register hold the time from the moment the ICP event (Signal at ICP pin) happens, the TCNT1 is delayed by a few cycles it takes to start the interrrupt and read the register.
I don't know how fast the Code is, but with signal frequencies above about 5-10 kHz you may need a little more than 1 MHz clock.

The Amplitude from a headphone jack should be about 0.2-2 V. It is probebly not large enough to drive digital input. However it should be enough for the analog comperator, that can also be used with the icp function. An alternative would be adding an simple amplifier and limiter.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There's still (unless you corrected as per my earlier post) nothing that starts the timer. The first if statement it the interrupt says "if the timer is already running, start it". That means if it's not running, it won't start.

Also, like Kleinstein said, the headphone jack is likely <2V depending on volume, so it probably won't trip if you just connect it straight. Unless you run the AVR @ 3.3 V and crank the volume to max maybe.

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In that same if clause that's supposed to start the timer, you should set the counter to 0, as well, as Kleinstein mentions.

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks everyone.

I fixed the if clause that was checking for the timer so I should be starting it now. On a sidenote, I am also checking CS11 and CS12 since it takes all three to set the timer up correctly.

I thought variables on AVRs were guaranteed to be initialized to 0. I just took a quick look through AVR035 and I didn't find it. I also just tried looking through the forums but I am not finding the conversation :-( I know I have read that somewhere.

Anyway, when I turn my timer off, I reset everything back to 0. I could initialize them to 0 when I turn the clock on, it doesn't hurt anything. It will be like adding suspenders when one is already wearing a belt but it ensures one's pants don't fall down.

The project tonight is to figure out the analog comparator. Are there any gotch-ya-s that I should look out for? I am still working my way through the datasheet so I am not willing to scream for help yet.

I also had a moment of inspiration at work today about the differences between TCNT1 and ICR1. Yes, if you get to ICR1 immediately after the interrupt files, the two registers will be the same but ICR1 will hold the time the interrupt fired while TCNT1 will keep counting at the clock frequency. I know. Not news to the experts but an important difference to us new guys.

You can have my mac when you pry my cold dead fingers off of it.

Kevin McEnhill -- mcenhillk@gmail.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mcenhillk wrote:
I thought variables on AVRs were guaranteed to be initialized to 0.
Look in the data sheet where that register is described. In the table where it shows what each bit means, it will also show you the default value of each bit. In the case of TCNT1, it's 0 at init.

mcenhillk wrote:
if you get to ICR1 immediately after the interrupt files, the two registers will be the same but ICR1 will hold the time the interrupt fired while TCNT1 will keep counting at the clock frequency.
That's why I brought it up earlier. However, it'll only mean a difference of a few clock cycles, which if you don't need precision shouldn't mean much to you.

Clancy _________________ Step 1: RTFM Step 2: RTFF (Forums) Step 3: RTFG (Google) Step 4: Post

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I thought variables on AVRs were guaranteed to be initialized to 0.

That depends what you mean by "variables". Maybe you meant the Special Fuction Registers (SFR)s that Clancy already answered - their states are shown in the datasheet. But if you are talking about variables in your C program this has nothingm specifically to do with the AVR or even Atmel. That behaviour is dictated by the C standard. Globals (variables outside functions) and statics within functions for which you provide an initial value in the C code are guaranteed to hold that value when the C program starts and those same types of variables, if no initialisers are given are guaranteed to hold 0 when you first access them (the former are known as .data and the latter as .bss) but any variable you create within a function (and that isn't 'static') has no guarantee about what its initial value will be - they are created on the stack and, because it may already have the remanants of previous calls/rets and other previous stack variables you will likely find that any such automatic(ally created) variables will contain completely junk values. Hopefully if you make a read access to one without code to first set a value your compiler will produce some kind of warning such as "variable is accessed before its initialised" or something - it's generally very unwise to ignore such warnings and there may be some circumstances in which the compiler can't "see" this happening. While it adds code it's usually safest to specify initial values for locals like this to avoid nasty accidents.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am missing something basic here. If I can get over this analog comparator feeding the input capture problem, I am done! I am almost there and I am pretty sure that light at the end is not a train.

I got a rudimentary analog comparator program working this morning after chasing some basic electronics problems (I am starting to learn that 1M resistors are your friends).

Now, I am trying to take what I learned in the prototype code and incorporate it in the program. Before this morning, I would be doubting my code but now I am starting to lean toward the HW side of things. Do I have to do anything to the output of an mp3 player before I feed it into AIN0 and AIN1?

#include 
#include 

void ioinit (void);

int main (void)
{
    ioinit ();

    TCCR1B |= (1 << ICES1); // Input Capture Edge Select
    TIMSK |= (1 << TICIE1); // Enable Input Capture Inerrupts.

    ACSR |= (1 << ACIC); // Analog Comparator Input Capture

    sei (); // Turn on global interrupts.

    for (;;)
    {
        // Watch the daisies grow.
    }
}

void ioinit (void)
{
    DDRA = 0xff; // All outputs

    DDRB = 0x00; // All inputs
    PORTB = 0x00;
}

/*
 * Turn timer off.
 *
 * If the timer overflows, the control tone has stop. Therefore,
 * the timer is reset for the next control tone by turning off
 * and resetting the counter back to zero.
 */

ISR (TIMER1_OVF_vect)
{
    // TODO Save register state and turn off interrupts
    TCCR1B |= (0 << CS10);
    TCNT1 = 0;
    ICR1 = 0;
    // TODO Restore register and turn interrupts back on.
}

/*
 * Start timer and measure the time between interrupts.
 *
 * Check Timer Control Register B to see if the timer is on. If
 * the timer is off, turn it on and return from the interrupt.
 *
 * The second time through will have the time since the first
 * interrupt fired. An integer devide will take care of any slop
 * in the timing and determine which case to run from the switch
 * statement. The appropriate pin is toggled and the counter is
 * set to a non-activating place holder value.
 *
 * Any subsuquent time the interrupt fires will reset the
 * counter value back to the place holder value. This prevents
 * the output from toggleing back and forth for the 0.5 to 1.0
 * second the control tone is active.
 */

ISR (TIMER1_CAPT_vect)
{
    // TODO Save register state and turn off interrupts
    if (!(TCCR1B & CS10)) // TODO There are two other bits to check.
    {
        TCCR1B |= (1 << CS10); // Start timer

        TCNT1 = 0;
        ICR1 = 0;

        return; // Return from interrupt
    }

    if (ICR1 > 5000)
    {
        ICR1 = 5000;
        return; // Return from interrupt
    }
    
    switch (ICR1 / 100)
    {
        case 1:      // Day01 PA0  150us 6667Hz
            PORTA ^= (1 << 0);
        break;
    
        case 2:      // Day02 PA1  250us  4000Hz
            PORTA ^= (1 << 1);
        break;

// Trimmed a bunch of other cases.

        case 13:     // Reset     1350us   741Hz
            PORTA = 0x00;
        break;

        default:     // We should not be here
            PORTA = 0xff;
        break;
    }

    ICR1 = 5000;
    // TODO Restore register and turn interrupts back on.
}

You can have my mac when you pry my cold dead fingers off of it.

Kevin McEnhill -- mcenhillk@gmail.com