NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Double tap program, need help points?

September 29, 2012
by wadaltmon
wadaltmon's Avatar

Now that I have the basic I/O circuit built, I am looking to make a newer project, where the output yields true (tied into ground) on the second push of a button being pressed twice in rapid succession (i.e. button is pressed, released quickly, then pressed a second time, and while the second press is held, the output yields true). Here is some pseudocode I wrote a while back to illustrate what I am trying to do...

•A. Wait in loop for button to be pressed

•B. When the button is pressed (tied to GND)

      -B1. Wait for it to be released

      -B2.When it is released:

           B3•wait 0.1 seconds, increase wait variable by 0.1

           B4•has it been more than 0.5 seconds since it was released (wait variable > 0.5)

                 B5-yes: go back to A

                 B6-no: is the button being pressed (tied to GND) now?
                        B7 - no: go back to B3

                        B8 - yes: activate output (ground out of output Pin)

•C. While the output is true

        -C1: is the output released?

               C2 - no: go back to C1

               C3 - yes: stop output and return to A

The problem is, I have no idea how to even begin on making an algorithm for this code. I need some ideas (let's assume the switch is already debounced). Where should I start on making this code, also assuming that I have the basic I/O code already done and working?

Thanks,

Dalton

September 30, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Dalton...

Since it is time you intend to measure, the first thing you need to do is to get a "time base".

(I ALWAYS have a "time base" in any program I make, since nearly everything that I do involves time in some sort of manner, and using a "time base"/"interrupts" is the ONLY WAY to manage more than 1 process effectively.)

The "time base" will have to be interrupt driven using one of the timers on the Microcontroller.

I would get the "time base" to be 10,000 times per second, but 100 times per second would be okay for this. (Maybe the 100 times per second would be easier, since the accumulated counts would be smaller. For me 10,000 times per second takes are of nearly EVERY situation I find myself in, so I just always us that.)

For what you need all that is necessary is a single counter, or you can use something more significant. (For hear I will just assume you are using a "single counter", "counter1", that gets incremented each tick of the "time base"...)

Then I would divide your program into a series of conditions, events, and actions.

Then in your main loop, or the loop that validates this switch sequence, you check the conditions and times.

i.e.


Condition - No Key Pressed


_ If button is pressed

__ __ clear counter1

__ __ Set Condition 2 - Key Pressed


Condition - Key Pressed


_ If button is pressed

__ __ Set Condition 3 - Double Tap

_ If counter1 > maxwait_button

__ __ Set Condition 1 (give up on waiting for "double tap")


Condition - Double Tap


_ Do whatever you want to do on "double tap"

_ Set Condition 1 - No Key Pressed

That's that...

NOTE: The algorithm assumes that your "button debounce function/hardware" is also taking care to ONLY return a new button event press if the button has been released in between.

If your "button debounce function/hardware" IS NOT taking care of that then a new condition needs to be added to "condition - button release".

PCBolt has pointed out recently that there is a Tutorial called "Crystal Real Time Clock". You can use that as the basis to learn how to drive your "time base".

I often find that "realtime", as in clock time, is helpful as well as counters. Depending on the complexity of your code you may find it helpful to have multiple counters dedicated to multiple processes, or even temporary counters that can be reused. NONE of that is necessary for what you want to do here though.

September 30, 2012
by wadaltmon
wadaltmon's Avatar

Wow, I never thought about using the crystal itself to measure time. Cool stuff! And I will try out a switch statement, I never thought of that. However, how do I set one up (like, at the interrupt, how do I call the statement within the switch)?

And interesting thing, interrupts. I know how they work, but I have never understood where to put them in the code. Is it outside of the while loop, or within it?

September 30, 2012
by JimFrederickson
JimFrederickson's Avatar

Interrupts, pretty much by definition, are "outside of any code loop".

They are, essentially, "autonomous/standalone functions" that are executed based on some event occurrence that the Microcontroller Monitors/Looks for.

There are numerous possible sources for interrupts.

CARE must be taken to keep the code in the interrupt functions fast and efficient. So ONLY do the minimal amount of coding/processing necessary within the interrupt functions and then do additional coding/processing that is handled in your main loop.

As noted in my post the "Crystal Realtime Clock" in the Tutorials is a good place to start... (On the Nerdkits Webpage select "Tutorials" from the top then scroll to about the middle for the "Crystal Realtime Clock".)

That should give you a basis to understanding how interrupts are coded and how they work.

In that example they use:

SIGNAL(SIG_OUTPUT_COMPARE0A) {
}

As the function to code their interrupt code in.

For me I have been using the "ISR" format:

ISR(TIMER0_COMPA_vect)
{
}

I like the "ISR" format because it is easier for my brain to determine that it is handling an interrupt. ("ISR" stands for "interrupt service routine"...)

But in any case...

If you use that example code it is a good basis for what you are trying to do. (In that example a single variable called "thetime" is incremented to count time, for your purposes you can create a variable like "counter1". To track your event times. Basically "counter1" just gets incremented in the "interrupt function" and in your main loop, when you are timing something, just zero "counter1" in your "main function" and then check in your "main function" to see when a value is reached.

With a little more coding you can also make it into a "count down counter" instead of a "counter" too, which I often find helpful. Not to mentioned that "comparing to 0" is a little more efficient as well.)

The Tutorial goes over the use of "volatile" as well.

That is VERY important that variables that are changed in "interrupt functions" are defined as "volatile". Otherwise it is possible your main loop will not get the correct values.

October 02, 2012
by wadaltmon
wadaltmon's Avatar

I studied the code, and I still don't understand a few things. I understand that sei() turns on the ability in the main code to handle interrupts. I also now understand the function of the interrupt label. However, what I don't get is really the "realtimeclock setup" part of the code. What does the code:

// setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 143;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);

...what does it accomplish? What do each of these variables mean? Also, how does the ISR or SIGNAL command know to compare a certain value to something else if the argument within it has never been mentioned before? What does that argument mean (i.e. signal_compare0a, etc.)?

October 02, 2012
by JimFrederickson
JimFrederickson's Avatar

I had thought the remark statements did a good job explaining what they are doing...

What you should do, which I think maybe you haven't, is get and look over the full Datasheet for the Atmel AVR Micrcontroller. (Usually I just go to "Mouser.com" or "Digikey.com" and search for the part to get the Datasheets. The Atmel Site has them as well, I just find the store sites to be quicker to get around in and usually I think of getting them while I am there in any case...)

NOTE: Many Datasheets come in different, usually 2, sizes... If you do not get the "Full Datasheet" there may be some things you won't find.

Don't expect to get the Datasheet and read through it like a novel and understand everything. (There are not many people I know that can do that, there is alot of stuff in there.)

Do read/look through it and see what is there. Pay extra attention to the things that are of interest to you specifically. Then use it as a reference to get the details for things when you need them.

As far as the code you pasted...

To control various functions/abilities on the Microcontroller there are a series of registers that can contain byte/word/bit values. (In the Datasheet you can look at the "Register Summary" to see all of them in one table.)

These values will select and define various capabilities of the Microcontroller. (The Datasheet will detail all of these settings.)

Basically the code you pasted is taking a bit value of "1" and shifting it to the proper position to be stored in a register in order to enable the Timer how this program wants to use it.

So "WGM01" is a constant in the code referencing the bit position, but if you search for "WGM01" in the Datasheet it will tell you what it is used for...

In total there are 26 sources for interrupts that are possible. (On the Microcontroller there is circuitry that monitors for these events/conditions and causes the program to execute different code when necessary.)

Whenever any interrupt occurs it causes the Microcontroller to save a minimal amount of register data on the Stack as well as the current Program location then a jump to a specific place in program memory where there is usually a jump instruction to the actual code to handle that interrupt will occur.

NOTE: Since the "C Language" is being used there are more things saved onto the stack that what the Microcontroller does by itself. The Microcontroller only saves the 'minimal amount necessary' on the stack to be as fast/efficient as possible, and anything else is necessary is the responsibility of the programmer/compiler.

Here are the possible interrupt sources...

Interrupt Vectors

Vector No. Program Address Source Interrupt Definition

1 0x000 RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
2 0x001 INT0 External Interrupt Request 0
3 0x002 INT1 External Interrupt Request 
4 0x003 PCINT0 Pin Change Interrupt Request 0
5 0x004 PCINT1 Pin Change Interrupt Request 1
6 0x005 PCINT2 Pin Change Interrupt Request 2
7 0x006 WDT Watchdog Time-out Interrupt
8 0x007 TIMER2 COMPA Timer/Counter2 Compare Match A
9 0x008 TIMER2 COMPB Timer/Counter2 Compare Match B
10 0x009 TIMER2 OVF Timer/Counter2 Overflow
11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event
12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x00D TIMER1 OVF Timer/Counter1 Overflow
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A
16 0x00F TIMER0 COMPB Timer/Counter0 Compare Match B
17 0x010 TIMER0 OVF Timer/Counter0 Overflow
18 0x011 SPI, STC SPI Serial Transfer Complete
19 0x012 USART, RX USART Rx Complete
20 0x013 USART, UDRE USART, Data Register Empty
21 0x014 USART, TX USART, Tx Complete
22 0x015 ADC ADC Conversion Complete
23 0x016 EE READY EEPROM Ready
24 0x017 ANALOG COMP Analog Comparator
25 0x018 TWI 2-wire Serial Interface
26 0x019 SPM READY Store Program Memory Ready
24 0x017 ANALOG COMP Analog Comparator
25 0x018 TWI 2-wire Serial Interface
26 0x019 SPM READY Store Program Memory Ready

Lastly the ISR or SIGNAL commands are not doing anything that 'they know about'...

What they are doing is telling the compiler to insert your code to be executed when the Microcontroller executes a specific interrupt. In this case it is "Interrupt Condition 15".

October 02, 2012
by wadaltmon
wadaltmon's Avatar

So, TCCR0A |= (1<<WGM01); clears the timer, TCCR0B |= (1<<CS02) | (1<<CS00); tells the source of the clock increments, OCR0A = 143; sets the limit to clear the timer at, and TIMSK0 |= (1<<OCIE0A); makes it happen when it needs to, right?

So, in the end, I will have to use a pin change interrupt on PCINT. Okay, so I would use something like ISR(PCINT0_vect){whatever happens when the button is pressed, i.e. switch stament}, right?

I will run through the data sheet tonight, and look at some of the interrupt stuff I know is on there.

October 02, 2012
by JimFrederickson
JimFrederickson's Avatar

For me I always try to do things in ways that are "simple for me and that I can understand easily".

The result of this is that I don't always end up with the most elegant solution. (One of the simplest examples of this are some of the methods for coding things in "C". Many of those methods I find terribly brain wracking, so I break them up into separate statements.)

Technically what does do:

TCCR0A |= (1<<WGM01)

It takes a "1" bit value shifts it left the number of times that the constant WGM01 is set to then it takes that result and does a "bitwise or assignment" with register TCCROA in the Microcontroller.

The ends up setting the WGM01 bit in the TCCROA register to 1. (Turning it on.)

(wikipedia.com and search for "operators in c" for a discussion on this.)

Technically:

TCCR0B |= (1<<CS02) | (1<<CS00)

Is setting the "clock source" for the timer.

In this case it sets the "clock source" to be "System clock / 1024". The result is that the system clock ticks are feed into a prescaler unit that sends a clock tick to the timer once every 1024 system clock ticks.

(The "clock increments" are 1.)

Yes the last part "makes it happen when it needs to", if by that you mean "generates and interrupt when the counter limit is reach.

At far as the PCINT interrupt there is more involved than that...

Pin change interrupts are expecting signals that are already "validated", or at the very least an "electrically conditioned signal" to remove the noise...

What appears to be a simple "on/off" to our perceptions is quite different if you are looking at millions of times per second.

Basically when a mechanical button is pressed it goes from "off -> static -> on". (Then the reverse when released.)

The process to remove the static associated with a button press is "debouncing". There are hardware and software methods to "debounce" a button.

I tend to do as much as possible in "software" since for me it is more flexible and if there is less hardware to there less solder. Most of the time for me simply "debouncing" a button is not what I need, I need to "validate" the button to some degree in addition.

In the Nerdkits Forums there are discussions on this. Recently under "Project Help and Ideas" and "Push Button combination lock".

My rule of thumb for interrupts is that

"If it is something that happens fast or that needs to be done in realtime then use an interrupt, otherwise poll".

So for me a "button press" is something that I would poll for, but I am by no means a consensus on this.

An additional reason to poll for a button is that it doesn't matter which i/o pin the button is connected to, you can just change the connection and change your code. If you are using an interrupt then only certain i/o pins facilitate that.

October 02, 2012
by wadaltmon
wadaltmon's Avatar

I am better with simple hardware rather than programming debounces, and I already have a circuit with a 555 timer that can facilitate the debounce.

And I tried polling for the button press, but it always seemed to be more efficient to use an interrupt (minus the learning factor), because of the separate press, release, second press held events.

Simply pressing the button won't work on this little project, simply because it is a "safety lock", so that the device doesn't misfire somehow.

Yes, I understand that only some I/O pins can work with an interrupt. So, that is why I would use PCINT0, 1, or 2... so that I could have an interrupt.

So basically, do you think I should just have a simple poll for the button for what I want to accomplish, or should I use interrupts?

October 02, 2012
by JimFrederickson
JimFrederickson's Avatar

I had noted before that people must use what they are comfortable with...

What works for one person may, or may not, work for another person for a variety of reasons...

So I will address my argument/reasons for using software.

For me, and my tastes, a 555 timer is not a good option.

8 pins, 2 resisters, a capacitor, and a switch? (Forgive me I haven't used a 555 timer, or any timer for that matter, since about 2 years after the PICs were introduced. Sorry as well about the competitions references, BUT I SOLELY USE AVR's now : ) )

About 12 solder points?

For each switch?

I can have 16 buttons easily managed with debounce, and associated press/release, hold events all by the AVR with only the buttons and connecting wire/traces. (In this case, for me, the buttons are arranged as 4 rows of 4. I use 4 i/o pins for sensing state and 4 i/o pins for row Power/GND. So I muliplex the row i/o pins and check the state with the other i/o pins.)

For me the trusty little AVR and some software is a much better solution.

After-all most of the time the AVR is sitting in a circuit it is only using a fraction of it's capability so why not use more of it's potential rather than adding more hardware?

There is already a pull-up resister available for each i/o pin VIA software, so a simple button to GND is no additional hardware.

You have mentioned press, release, second-press, held events.

Can you account for these all with your 555 timer?

Maybe... (Maybe "you", or someone, I already know I couldn't at this moment.)

What if you want to "change the behavior" later on in your application development?

Unsolder, solder more?

Maybe a new interface board? (Between the AVR and the Button...)

All of those can be events can be taken care of in software.

Also there is flexibility in testing...

Maybe when I first am validating the the circuit is working properly I treat the buttons as switches. (In fact maybe the circuit has a combination of buttons and switches, but I treat them all as switches.)

Then later on in my development cycle I change, in software, how the buttons/switches are handled.

Lastly...

Ultimately you are "programming a microcontroller for a task" as a learning tool and for experience.

Why not further your software learning and experience by doing more things in software?

October 02, 2012
by wadaltmon
wadaltmon's Avatar

Well, ultimately, I want to go into computer engineering. I also like programming, but I am not really good at it at all... which is why I want to learn.

Thing is, I plan on making this into a small wrist-mounted gadget. I will use sockets for all the components, so that I can take them out, replace them, etc. if needed.

And, with the 555 timer, it's something I built long before I got the ATMega, so I decided I would just use it rather than go through a process to debounce a switch with programming, considering I already had the circuit built.

A while ago, I already designed a circuit to accomplish this task. HOWEVER, it was too large to fit on the wrist and after a long while of attempting to reduce it, I just switched over to trying to use the ATMega.

And I see what you mean, but as I said, I already have the circuit built. And I would need another 555 timer to do that, and I haven't the slightest idea how. My electronics background is almost exclusively digital, not a lot of analog. I decided to move to programming and circuitry integration after a while, get the best of both worlds.

I think that it would be best to use a polling bit for this button project. I have some code already written, but it hasn't managed yet. I will post it here after I think I have it finished.

Overall, I would like to learn interrupts. The problem is, I understand ZIP about them. I get how they work, and when they are used and all, but I don't understand setting them up and calling them. To me, it just looks like a jumble of letters, frankly. When I look at the Nerdkits tutorial videos, it seems like they are trying to make it easy, but of course, it's just a small video, and they don't have time to explain the code like they do in the USB Nerdkit guide, which I learned fantastically with.

I think I will take a look at the PS/2 keyboard tutorial to try and get more interrupt experience. In the meantime, I will work on a piece of code that simply polls for the button and uses the counter, and submit it here.

Thanks for all the help, JimFrederickson. I will put up the code here in a bit and see if it's what you mean by just polling for the button.

October 02, 2012
by Ralphxyz
Ralphxyz's Avatar

Polling versus using Interrupts is often discussed in some forums with a lot of anger, yelling and swearing.

I think it manly has to do with what else your mcu is needing to do.

If the mcu isn't needing to do much else but poll the switches and post a status to the lcd, polling will be fine.

But if you are measuring temperatures on five sensors and doing a lot of serial communications then maybe an interrupt would be better.

Ralph

October 02, 2012
by Ralphxyz
Ralphxyz's Avatar

Oh also there is built in debounce on PB0!

Ralph

October 02, 2012
by pcbolt
pcbolt's Avatar

Dalton -

There was a recent forum discussion here about using debouncing and timer interrupts. I posted a link (right around the Sept 24th date) that led to an article about software and hardware debouncing. The hardware discussion centered around set/reset circuits and RC circuits (with optional diode) that looked pretty interesting. If you are more of a hardware guy, it's worth a look. Using a 555 seems like a very similar solution. I posted some timer code there as well for debouncing a button but you'd have to add some flag variables and tests to do what you'd need. I would shy away from pin change interrupts since pressing a typical button once will trigger 10 to 30 interrupts each press. I pulled my hair out trying to get that to work and finally went to a single timer interrupt to do everything.

October 02, 2012
by wadaltmon
wadaltmon's Avatar

Ralphxyz - I've seen some of that, and I really don't understand what makes people so angry about it... it's a project for fun, really... but oh well, that's them, and this is us. And thanks for the input on PB0, that is actually really cool!

Pcbolt - I've been following the combination push button lock for a bit, and I've read what you've said on it. Yeah, I've used a 555 circuit I built a while ago to debounce, and judging by my oscilloscope, it looks great! And by RC, you are talking about an RC latch and RS latches? I have looked into those, but really, the thing is, the output is almost the same, it's just the bounces are smaller waves on the oscilloscope. And yes, I am using a poll right now and then maybe I will use an interrupt.

October 03, 2012
by pcbolt
pcbolt's Avatar

Dalton -

The RC circuit just has a couple of resistors and one capacitor that acts like a low pass filter letting DC through and sinking the AC (aka debounce noise) to ground. Haven't tried it yet but the circuit is on the link posted in the other thread and also gives some equations to set it up.

October 03, 2012
by wadaltmon
wadaltmon's Avatar

Ok, PCBolt, I'll check it out.

Well, I new have the double tap code ready for my first test (I am at school right now, so I can't test it until later), but I think this will work. Thanks to JimFrederickson for helping with all of this. Here it is: (comments are for my future reference)

/* DOUBLE TAP CODE FOR USE WITH NERDKITS
BY DALTON SCOTT*/

/* Shooter double tap code test
uses a switch statement to toggle between the several states of the shooter.
We started with a simple I/O circuit, allowing an LED to turn on while a button was being pressed.
now we are stepping it up to next level, by adding a switch statement to allow us to have a double tap
circuit. We begin by copying our simple I/O into our program here.*/

/* Once that is done, we can begin to allow our switch statement cases to be defined as below.*/
#define F_CPU 14745600 //CPU speed by crystal

#define STATE_FIRSTPRESSED 1 //first case of switch: first time pressed
#define STATE_FIRSTRELEASED 2 //second case of switch: first time released
#define STATE_SECONDPRESS 3 //third case of switch: the button is being pushed the second time, and shooting happens
#define STATE_BUTTONFLOAT 0 //only other thing that can happen, the button is not being pressed and nothing is happening.

#include <stdio.h> //basic I/O stream file

#include <avr/io.h> //more I/O
#include <avr/interrupt.h> //enables interrupts
#include <avr/pgmspace.h> //enables space use on the chip
#include <inttypes.h> //allows us to use C language variables instead of those just for the ATMega with its normal bootloader

#include "../libnerdkits/delay.h" //nerdkits library

// PIN DEFINITIONS:
// PC2 - TX PUSHBUTTON (pin 25)
// PB5 TX output indicator LED (CATHODE to pin 19)
int counterfloater = 0; //counter for floating button, allows us to have a loop so we can warm up the avr
int counterfp = 0; //counter for function within the first time it is pressed
int counterfr = 0; //counter for the first time the button is released.
int countersp = 0; //counter for the second time the button is being pressed. Arbitrary, but keeps the counter state warmed up.

int STATE = 0; //when we begin the program, we are in the floating state.
int main() {

// Set the output pin (pin 19)
  DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
  DDRC &= ~(1<<PC2); // set PC2 as input

// turn on the internal resistor for the pin
  PORTC |= (1<<PC2); // turn on internal pull up resistor

// declare the variables to represent pushbutton input
  uint8_t mode_select;

  while(1) { //main loop
mode_select = (PINC & (1<<PC2)) >> PC2;

/* basic I/O code below

if (mode_select == 1) // Pushbutton switch "open"
{
  PORTB |= (1<<PB5); // LED is OFF            
}
if (mode_select == 0) // Pushbutton switch "closed" (tied to GND)
{ 
  delay_ms(29);
  PORTB &= ~(1<<PB5); // LED is ON
} 
} 
  return 0; //nothing is returned, so the program starts over

}

*/

//here is where we begin the code for our double tap.
 switch(STATE) {
    case STATE_BUTTONFLOAT: //while we are not attempting to shoot and the button is being left alone
        /*all this counting stuff in here just gives us an arbitrary setup
        so that we can switch to the next state without having to use an interrupt signal
        routine. This also allows the counter function on the AVR to get initialized
        so it does not take an extra amount of time when it is no longer idle
        and actually processing.*/
        if (counterfloater > 2){ 
            counterfloater = 0;}
        else
        if (counterfloater <=2){
            if (mode_select == 0){
                counterfloater = 0; //don't want anything mucking up our memory, so we reset this so we can reuse it later
                STATE = 1;}
            else
            if (mode_select == 1){
                STATE = 0;}
            }
        }
    case STATE_FIRSTPRESSED: //first time they press the button in the process of trying to shoot
        /*when you are pressing the button for the first time, we have to make sure that 
        the user is only pressing the button for a certain amount of time, like 0.5 seconds, and 
        then releasing it before the counter gets to its limit. This is also possible with 
        an interrupt service routine, but that is just too much for me to handle.

        If we look at the code for the real time clock, we can see that 144 cycles (updates) 
        makes 100 hundredths of a second, i.e. 1 second. Therefore, we want 72 updates of the
        count variable to make 0.5 seconds, and around 45 cycles to make it go to 0.3 seconds. You
        can use any of these measurements to make your little cycle, but I am going to go with 0.5 seconds.*/
        if (counterfp <= 72){ //if we are within the time limit
            if (mode_select == 1){ //and if the button is not being pressed anymore
                counterfp = 0; //once again, clear the variable so we can use it again and make our memory clear.
                STATE = 2; //change the state of the button to when it is first being released
                    }
            else
            if {mode_select ==  0){ //if we are in the time limit and the button is still being pressed
                counterfp += 1; //then the counter goes up by one
                }
                }
        else
        if (counterfp > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
            counterfp = 0; //clear variable for other memory use and cleared for later
            STATE = 0; /* since they are outside the time limit, we can just assume the user 
                    did not press the button for long enough, or that they were just picking something
                    up. This is why we do the double tap, to avoid misfires when we don't actually
                    WANT to fire.*/
                }
    case STATE_FIRSTRELEASED: //when they release the button between the two presses.
        /* the best part about this state is that the code is basically the EXACT code that we just used
            for the first press. Since we are waiting the same time increment, we just have to switch
            counterfp for counterfr and change around the mode_select values, since we are checking for
            if to be pressed again, not released. I proceed to copy the code into here.*/
        if (counterfr <= 72){ //if we are within the time limit
            if (mode_select == 0){ //and if the button is being pressed again
                counterfr = 0; //once again, clear the variable so we can use it again and make our memory clear.
                STATE = 3; //change the state of the button to when it is being pressed a second time
                    }
            else
            if {mode_select ==  1){ //if we are in the time limit and the button is still not pressed
                counterfr += 1; //then the counter goes up by one
                }
                }
        else
        if (counterfr > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
            counterfr = 0; //clear variable for other memory use and cleared for later
            STATE = 0; /* since they are outside the time limit, we can just assume the user 
                    did not have the button released for long enough, or that they were just picking something
                    up. This is why we do the double tap, to avoid misfires when we don't actually
                    WANT to fire.*/
                }
    case STATE_SECONDPRESS: //when they are pressing it a second time.
        /*since this is a continuous state, and has no time limit like the other ones did, we can actually
            reuse the code from the floating state, we just have to change some things around. I proceed
            to copy in the code from the float state.*/
        if (countersp > 2){ //if we are outside of the arbitrary time limit
            countersp = 0;} //put ourselves back in the arbitrary timer limit
        else
        if (countersp <=2){ //if we are in the arbitrary time limit
            if (mode_select == 0){//and the button is still being pressed here
                delay_ms(29); //little debounce here, nothing huge.
                PORTB &= ~(1<<PB5); //output is true.
                }
            else
            if (mode_select == 1){//if we are not pressing the button anymore
                countersp = 0; //reset our arbitrary second push counter for use later
                STATE = 0;} //and return back to floating state, since we're not pressing it anymore.
            }
        }
    }
    return 0; //nothing is returned, so we go back to the beginning of the program.
}

/*well, that's it for the double tap program. There are several ways to do this, but this is just one example that I thought
would be simple and not take up a lot of memory. One other way to do this is by using interrupts, which are actually really
useful when thinking about outside events. However, it takes a lot of setting up and weird variables to make. That is why
I don't use them, and rather set up code that later I can look at and understand why I did it.*/
October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

What I see is the direction I was discussing, but there is a few things I would change.

1 - I think there are some problems with your braces...

2 - The second "return 0" is a problem of some sort.

3 - There are references to an interrupt based time source, but then there is a part that says it is not using interrupts?

4 - I like to be able to read my code easily so for this:

if (mode_select == 1) // Pushbutton switch "open"
    {
    PORTB |= (1<<PB5); // LED is OFF           
    }

if (mode_select == 0) // Pushbutton switch "closed" (tied to GND)
    {
    delay_ms(29);
    PORTB &= ~(1<<PB5); // LED is ON
    }

I would use something like this:

if (state_button_pressed) // Pushbutton switch "closed"
    {
    PORTB &= ~(1<<PB5); // LED is ON
    }
else 
    {
    PORTB |= (1<<PB5); // LED is OFF           
    }

For me this is a bit more readable, and also only one condition is executed rather than checking two conditions on each pass of this code in the program.

Also I took out the delay, but I will mention that next.

5 - The:

delay_ms(29);

I am not sure why these are here, but they are going to cause the program to "stutter"...

Every 1ms delay is 14,000+ cycles that are not being used.

It's almost 3/100's of a second.

If you are doing ONLY hardware debounce it is probably not going to have any affect. If you are doing some sort of software debounce this will probably be noticeable.

Also, if you have these delays in several portions then the predictability of your loop is unknown because it becomes dependent on each condition on each pass of the loop that will decide to do a delay.

What I would do, if you are not going to use interrupts, is to just put a "delay_ms (1)" as the last statement in your "while". Then right after that update any "clock tick", "clock counter", or "clock count down counters" you will have to create the delays/timers you want/need

Then in the future if you do implement an interrupt driven time base then it is quite easy, but initially you can "test" the process without using and interrupt based time base.

Also this will give you a fairly consistent time base for what you are doing now with only 1 "delay_ms" function call.

6 - Regarding the "counter" you are using... int counterfp = 0; //counter for function within the first time it is pressed int counterfr = 0; //counter for the first time the button is released. int countersp = 0; //counter for the second time the button is being pressed.

There are 3, but won't 1 work? (Just an overall timeout to reset the "doubletap" process?

Or are these counters also, essentially, part of debouncing?

I was thinking that debouncing was being done in hardware for this?

If debouncing is not being done in hardware I would separate out the debounce process from the doubletap process.

7 - Your case statement:

 switch(STATE) {
    case STATE_BUTTONFLOAT:

I would change to something like:

 switch(state_doubletap) {
    case STATE_DOUBLETAP_FLOAT:

This way I can easily see that the case statement is in regards to our process of "DOUBLETAP" and the states I checked would be "STATE_DOUBLETAP_FLOAT" since C often uses all caps to delineate constants and I can see I am checking the "state" of the process "DOUBLETAP" to see if it is in "FLOAT".

I would apply a naming process similar for all variables used for each process as well.

October 03, 2012
by wadaltmon
wadaltmon's Avatar

So brackets, delete return 0;, and better labels.

It was just easier for me to comprehend if I used 3 counters.

And yes, this is what you discussed. For that, I thank you.

And I guess I assume wrong about the timing source, I will try better on that. But overall, I think the code (other than the understandability) is good after those changes, right? It should work?

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I fixed the problems, and get these errors now:

initialload.c: In function 'main':
initialload.c:92: error: case label not within a switch statement
initialload.c:108: error: expected '(' before '{' token
initialload.c:108: warning: statement with no effect
initialload.c:108: error: expected statement before ')' token
initialload.c:120: error: case label not within a switch statement
initialload.c:131: error: expected '(' before '{' token
initialload.c:131: warning: statement with no effect
initialload.c:131: error: expected statement before ')' token
initialload.c:143: error: case label not within a switch statement

make: *** [initialload.hex] Error 1

I haven't the slightest idea how to fix them. Any ideas?

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I got those errors with this code:

/* DOUBLE TAP CODE FOR USE WITH NERDKITS
BY DALTON SCOTT*/

/* Shooter double tap code test
uses a switch statement to toggle between the several states of the shooter.
We started with a simple I/O circuit, allowing an LED to turn on while a button was being pressed.
now we are stepping it up to next level, by adding a switch statement to allow us to have a double tap
circuit. We begin by copying our simple I/O into our program here.*/

/* Once that is done, we can begin to allow our switch statement cases to be defined as below.*/
#define F_CPU 14745600 //CPU speed by crystal

#define STATE_FIRSTPRESSED 1 //first case of switch: first time pressed
#define STATE_FIRSTRELEASED 2 //second case of switch: first time released
#define STATE_SECONDPRESS 3 //third case of switch: the button is being pushed the second time, and shooting happens
#define STATE_BUTTONFLOAT 0 //only other thing that can happen, the button is not being pressed and nothing is happening.

#include <stdio.h> //basic I/O stream file

#include <avr/io.h> //more I/O
#include <avr/interrupt.h> //enables interrupts
#include <avr/pgmspace.h> //enables space use on the chip
#include <inttypes.h> //allows us to use C language variables instead of those just for the ATMega with its normal bootloader

#include "../libnerdkits/delay.h" //nerdkits library

// PIN DEFINITIONS:
// PC2 - TX PUSHBUTTON (pin 25)
// PB5 TX output indicator LED (CATHODE to pin 19)
int counterfloater = 0; //counter for floating button, allows us to have a loop so we can warm up the avr
int counterfp = 0; //counter for function within the first time it is pressed
int counterfr = 0; //counter for the first time the button is released.
int countersp = 0; //counter for the second time the button is being pressed. Arbitrary, but keeps the counter state warmed up.

int STATE = STATE_BUTTONFLOAT; //when we begin the program, we are in the floating state.
int main() {

// Set the output pin (pin 19)
  DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
  DDRC &= ~(1<<PC2); // set PC2 as input

// turn on the internal resistor for the pin
  PORTC |= (1<<PC2); // turn on internal pull up resistor

// declare the variables to represent pushbutton input
  uint8_t mode_select;

  while(1) {  //main loop
mode_select = (PINC & (1<<PC2)) >> PC2;

/* basic I/O code below

if (mode_select == 1) // Pushbutton switch "open"
{
  PORTB |= (1<<PB5); // LED is OFF            
}
if (mode_select == 0) // Pushbutton switch "closed" (tied to GND)
{ 
  delay_ms(29);
  PORTB &= ~(1<<PB5); // LED is ON
} 
} 
  return 0; //nothing is returned, so the program starts over

}

*/

//here is where we begin the code for our double tap.
 switch(STATE) {
    case STATE_BUTTONFLOAT: //while we are not attempting to shoot and the button is being left alone
        /*all this counting stuff in here just gives us an arbitrary setup
        so that we can switch to the next state without having to use an interrupt signal
        routine. This also allows the counter function on the AVR to get initialized
        so it does not take an extra amount of time when it is no longer idle
        and actually processing.*/
        if (counterfloater > 2){ 
            counterfloater = 0;} //end counterfloater>2
        else
        if (counterfloater <=2){
            if (mode_select == 0){
                counterfloater = 0; //don't want anything mucking up our memory, so we reset this so we can reuse it later
                STATE = STATE_FIRSTPRESSED;} //end mode_select == 0
            else
            if (mode_select == 1){
                STATE = STATE_BUTTONFLOAT;}//end mode_select == 1
            } //end counterfloater <= 2 
        break;
    case STATE_FIRSTPRESSED: //first time they press the button in the process of trying to shoot
        /*when you are pressing the button for the first time, we have to make sure that 
        the user is only pressing the button for a certain amount of time, like 0.5 seconds, and 
        then releasing it before the counter gets to its limit. This is also possible with 
        an interrupt service routine, but that is just too much for me to handle.*/
        if (counterfp <= 72) { //if we are within the time limit
            if (mode_select == 1) { //and if the button is not being pressed anymore
                counterfp = 0; //once again, clear the variable so we can use it again and make our memory clear.
                STATE = STATE_FIRSTRELEASED;}//end mode_select == 1 //change the state of the button to when it is first being released

            else
            if (mode_select ==  0){//if we are in the time limit and the button is still being pressed
                counterfp += 1;}//end mode_select == 0 //then the counter goes up by one

                } //end counterfp <=72
        else
        if (counterfp > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
            counterfp = 0; //clear variable for other memory use and cleared for later
            STATE = STATE_BUTTONFLOAT;}//end counterfp > 72 
            /* since they are outside the time limit, we can just assume the user 
                    did not press the button for long enough, or that they were just picking something
                    up. This is why we do the double tap, to avoid misfires when we don't actually
                    WANT to fire.*/ 
        break;
    case STATE_FIRSTRELEASED: //when they release the button between the two presses.
        /* the best part about this state is that the code is basically the EXACT code that we just used
            for the first press. Since we are waiting the same time increment, we just have to switch
            counterfp for counterfr and change around the mode_select values, since we are checking for
            if to be pressed again, not released. I proceed to copy the code into here.*/
        if (counterfr <= 72){ //if we are within the time limit
            if (mode_select == 0) {//and if the button is being pressed again
                counterfr = 0; //once again, clear the variable so we can use it again and make our memory clear.
                STATE = STATE_SECONDPRESS;} //end mode_select == 0 //change the state of the button to when it is being pressed a second time

            else
            if (mode_select ==  1){ //if we are in the time limit and the button is still not pressed
                counterfr += 1;} //end mode_select == 1 //then the counter goes up by one
                } //end counterfr <= 72

        else
        if (counterfr > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
            counterfr = 0; //clear variable for other memory use and cleared for later
            STATE = STATE_BUTTONFLOAT;}//end counterfr > 72 
                    /* since they are outside the time limit, we can just assume the user 
                    did not have the button released for long enough, or that they were just picking something
                    up. This is why we do the double tap, to avoid misfires when we don't actually
                    WANT to fire.*/
        break;

    case STATE_SECONDPRESS: //when they are pressing it a second time.
        /*since this is a continuous state, and has no time limit like the other ones did, we can actually
            reuse the code from the floating state, we just have to change some things around. I proceed
            to copy in the code from the float state.*/
        if (countersp > 2){ //if we are outside of the arbitrary time limit
            countersp = 0;} //end of countersp > 2 //put ourselves back in the arbitrary timer limit
        else
        if (countersp <=2){ //if we are in the arbitrary time limit
            if (mode_select == 0){//and the button is still being pressed here
                PORTB &= ~(1<<PB5);}//end of mode_select == 0 //output is true.

            else
            if (mode_select == 1){//if we are not pressing the button anymore
                countersp = 0; //reset our arbitrary second push counter for use later
                STATE = STATE_BUTTONFLOAT;} //end mode_select == 1 //and return back to floating state, since we're not pressing it anymore.
            }// end countersp<=2

        break;
        }// end of switch
    delay_ms(1);
    }//end of while
} //end of main

/*well, that's it for the double tap program. There are several ways to do this, but this is just one example that I thought
would be simple and not take up a lot of memory. One other way to do this is by using interrupts, which are actually really
useful when thinking about outside events. However, it takes a lot of setting up and weird variables to make. That is why
I don't use them, and rather set up code that other people can look at and understand why I did it.*/
October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

There are just some "syntax" errors...

(More than just the braces... Look at the "basic I/O code below", that is an open remark block...)

In the "Programmer's Notepad" it is not matching all of the braces, which I just did a quick check in.

As the code was posted I don't think it will compile. (I am sure when you are home you will find out soon enough...)

Where you have your switch statement doesn't seem correct, but because of some of the other syntax issues I am not really sure what your intent is.

Just some things like that.

Labeling is of course personal choice.

I know alot of programmers that are quite good, but for me to read their code is huge PITA experience...

"Conditions, Events, Actions"...

That is what I always distill my Processes/Code down to.

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I redid all of the braces, though. Above are the errors I got when compiling. So, are you saying that you don't understand by indentations, or by the fact that my switch shouldn't be in the while loop :S I've always had it in my while loop.

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

The switch statement should be in the while loop...

"Above are the errors I got when compiling"?

I don't see any errors???

October 03, 2012
by wadaltmon
wadaltmon's Avatar

It's waaaay up there.

I fixed the problems, and get these errors now:

initialload.c: In function 'main':
initialload.c:92: error: case label not within a switch statement
initialload.c:108: error: expected '(' before '{' token
initialload.c:108: warning: statement with no effect
initialload.c:108: error: expected statement before ')' token
initialload.c:120: error: case label not within a switch statement
initialload.c:131: error: expected '(' before '{' token
initialload.c:131: warning: statement with no effect
initialload.c:131: error: expected statement before ')' token
initialload.c:143: error: case label not within a switch statement

make: *** [initialload.hex] Error 1

I haven't the slightest idea how to fix them. Any ideas?

October 03, 2012
by pcbolt
pcbolt's Avatar

Dalton -

I usually get those errors when I have a missing brace or two lying around. I think inside the "case(STATE_BUTTONFLOAT)" block there is a missing brace and there is an "else" that is just kind of floating there as well. All it takes is one missing brace to generate a bunch of errors the rest of the way. To narrow down the error, count how many times the compiler spit out and error and count up from the bottom that number of "case" statements to see where the trouble lies.

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

You still have some bracing off...
(maybe some on your remark blocks too...)

And you don't have any "break" statements. ("break" statements Here is the template that I use for switch statements...

switch (ipage) {
    case 0: {
        break;
        }

    case 1: {
        break;
        }           
    }

I don't think the brace grouping after the ":" in the case statement is necessary, but I always use it to be consistent.

The "break" statement though is necessary, otherwise the execution will just continue down...

If you post your current code, and don't care if I move columns around and stuff, I can re-arrange it...

In the Programmer's Notepad all of your "braces" and "parenthesis" pairs should highlight. (By default.)

If they don't then something is in error that confuses it. (I suppose there could be a "bug" in the Programmer's Notepad that may confuse it as well, but so far I have not found any that result in that situation...)

October 03, 2012
by wadaltmon
wadaltmon's Avatar

Here is the current code:

    /* DOUBLE TAP CODE FOR USE WITH NERDKITS
    BY DALTON SCOTT*/

    /* Shooter double tap code test
    uses a switch statement to toggle between the several states of the shooter.
    We started with a simple I/O circuit, allowing an LED to turn on while a button was being pressed.
    now we are stepping it up to next level, by adding a switch statement to allow us to have a double tap
    circuit. We begin by copying our simple I/O into our program here.*/

    /* Once that is done, we can begin to allow our switch statement cases to be defined as below.*/
    #define F_CPU 14745600 //CPU speed by crystal

    #define STATE_FIRSTPRESSED 1 //first case of switch: first time pressed
    #define STATE_FIRSTRELEASED 2 //second case of switch: first time released
    #define STATE_SECONDPRESS 3 //third case of switch: the button is being pushed the second time, and shooting happens
    #define STATE_BUTTONFLOAT 0 //only other thing that can happen, the button is not being pressed and nothing is happening.

    #include <stdio.h> //basic I/O stream file

    #include <avr/io.h> //more I/O
    #include <avr/interrupt.h> //enables interrupts
    #include <avr/pgmspace.h> //enables space use on the chip
    #include <inttypes.h> //allows us to use C language variables instead of those just for the ATMega with its normal bootloader

    #include "../libnerdkits/delay.h" //nerdkits library

    // PIN DEFINITIONS:
    // PC2 - TX PUSHBUTTON (pin 25)
    // PB5 TX output indicator LED (CATHODE to pin 19)
    int counterfloater = 0; //counter for floating button, allows us to have a loop so we can warm up the avr
    int counterfp = 0; //counter for function within the first time it is pressed
    int counterfr = 0; //counter for the first time the button is released.
    int countersp = 0; //counter for the second time the button is being pressed. Arbitrary, but keeps the counter state warmed up.

    int STATE = STATE_BUTTONFLOAT; //when we begin the program, we are in the floating state.
    int main() {

    // Set the output pin (pin 19)
      DDRB |= (1<<PB5);

    // Set the pin to input mode for Pushbutton
      DDRC &= ~(1<<PC2); // set PC2 as input

    // turn on the internal resistor for the pin
      PORTC |= (1<<PC2); // turn on internal pull up resistor

    // declare the variables to represent pushbutton input
      uint8_t mode_select;

        while(1) {  //main loop
        mode_select = (PINC & (1<<PC2)) >> PC2;
        //here is where we begin the code for our double tap.
            switch(STATE) {
                case STATE_BUTTONFLOAT: {
                    if (counterfloater > 2){ 
                        counterfloater = 0;} //end counterfloater>2
                    else
                    if (counterfloater <=2){
                        if (mode_select == 0){
                            counterfloater = 0; //don't want anything mucking up our memory, so we reset this so we can reuse it later
                            STATE = STATE_FIRSTPRESSED;} //end mode_select == 0
                        else
                        if (mode_select == 1){
                            STATE = STATE_BUTTONFLOAT;}//end mode_select == 1
                        } //end counterfloater <= 2 
                    break;}
                case STATE_FIRSTPRESSED: {
                    if (counterfp <= 72) { //if we are within the time limit
                        if (mode_select == 1) { //and if the button is not being pressed anymore
                            counterfp = 0; //once again, clear the variable so we can use it again and make our memory clear.
                            STATE = STATE_FIRSTRELEASED;}//end mode_select == 1 //change the state of the button to when it is first being released

                        else
                        if (mode_select ==  0){//if we are in the time limit and the button is still being pressed
                            counterfp += 1;}//end mode_select == 0 //then the counter goes up by one

                            } //end counterfp <=72
                    else
                    if (counterfp > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
                        counterfp = 0; //clear variable for other memory use and cleared for later
                        STATE = STATE_BUTTONFLOAT;}//end counterfp > 72 
                        /* since they are outside the time limit, we can just assume the user 
                                did not press the button for long enough, or that they were just picking something
                                up. This is why we do the double tap, to avoid misfires when we don't actually
                                WANT to fire.*/ 
                    break;}
                case STATE_FIRSTRELEASED:{
                    if (counterfr <= 72){ //if we are within the time limit
                        if (mode_select == 0) {//and if the button is being pressed again
                            counterfr = 0; //once again, clear the variable so we can use it again and make our memory clear.
                            STATE = STATE_SECONDPRESS;} //end mode_select == 0 //change the state of the button to when it is being pressed a second time

                        else
                        if (mode_select ==  1){ //if we are in the time limit and the button is still not pressed
                            counterfr += 1;} //end mode_select == 1 //then the counter goes up by one
                            } //end counterfr <= 72

                    else
                    if (counterfr > 72){ //however, if the user is outside the time limit, no matter the state of the button now...
                        counterfr = 0; //clear variable for other memory use and cleared for later
                        STATE = STATE_BUTTONFLOAT;}//end counterfr > 72 
                                /* since they are outside the time limit, we can just assume the user 
                                did not have the button released for long enough, or that they were just picking something
                                up. This is why we do the double tap, to avoid misfires when we don't actually
                                WANT to fire.*/
                    break;}

                case STATE_SECONDPRESS: {
                    if (countersp > 2){ //if we are outside of the arbitrary time limit
                        countersp = 0;} //end of countersp > 2 //put ourselves back in the arbitrary timer limit
                    else
                    if (countersp <=2){ //if we are in the arbitrary time limit
                        if (mode_select == 0){//and the button is still being pressed here
                            PORTB &= ~(1<<PB5);}//end of mode_select == 0 //output is true.

                        else
                        if (mode_select == 1){//if we are not pressing the button anymore
                            countersp = 0; //reset our arbitrary second push counter for use later
                            STATE = STATE_BUTTONFLOAT;} //end mode_select == 1 //and return back to floating state, since we're not pressing it anymore.
                        }// end countersp<=2

                    break;}
                    }// end of switch
            delay_ms(1);
            }//end of while
        } //end of main

    /*well, that's it for the double tap program. There are several ways to do this, but this is just one example that I thought
    would be simple and not take up a lot of memory. One other way to do this is by using interrupts, which are actually really
    useful when thinking about outside events. However, it takes a lot of setting up and weird variables to make. That is why
    I don't use them, and rather set up code that other people can look at and understand why I did it.*/

So, I can't find the errors in it. Maybe you can?

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

I this will compile now...

Some braces were missing, or in the wrong places.

I wouldn't normally have a remark at the end of the an "if" statement, but in this case it was helpful in determining where I think you wanted to have things grouped.

You will need to verify I have the proper blocks delineated as you had wanted.

If you put your cursor near a brace it should highlight that brace and the corresponding brace...

If you have a large block then you will need to drag the scroll bar down, or use the scroll This is ALWAYS a good check.

(If you can't highlight the brace after for the "main" function then and the the corresponding end highlighted then you can be pretty sure there is a problem!)

The vertical alignment lines for the blocks, if you indent in for each block, are nice too.
Mostly though I just use those for typing guides.

/*  DOUBLE TAP CODE FOR USE WITH NERDKITS
    BY DALTON SCOTT
*/

/*  Shooter double tap code test
    uses a switch statement to toggle between the several states of the shooter.
    We started with a simple I/O circuit, allowing an LED to turn on while a button was being pressed.
    now we are stepping it up to next level, by adding a switch statement to allow us to have a double tap
    circuit. We begin by copying our simple I/O into our program here.

*/

/*  Once that is done, we can begin to allow our switch statement cases to be defined as below.
*/

#define F_CPU 14745600          //CPU speed by crystal

#define STATE_FIRSTPRESSED 1    //first case of switch: first time pressed
#define STATE_FIRSTRELEASED 2   //second case of switch: first time released
#define STATE_SECONDPRESS 3     //third case of switch: the button is being pushed the second time, and shooting happens
#define STATE_BUTTONFLOAT 0     //only other thing that can happen, the button is not being pressed and nothing is happening.

#include <stdio.h>              //basic I/O stream file

#include <avr/io.h>             //more I/O
#include <avr/interrupt.h>      //enables interrupts
#include <avr/pgmspace.h>       //enables space use on the chip
#include <inttypes.h>           //allows us to use C language variables instead of those just for the ATMega with its normal bootloader

#include "../libnerdkits/delay.h" //nerdkits library

//  PIN DEFINITIONS:
//  PC2 - TX PUSHBUTTON (pin 25)
//  PB5 TX output indicator LED (CATHODE to pin 19)
int counterfloater = 0;         //counter for floating button, allows us to have a loop so we can warm up the avr
int counterfp = 0;              //counter for function within the first time it is pressed
int counterfr = 0;              //counter for the first time the button is released.
int countersp = 0;              //counter for the second time the button is being pressed. Arbitrary, but keeps the counter state warmed up.

int STATE = STATE_BUTTONFLOAT; //when we begin the program, we are in the floating state.

int main() {

// Set the output pin (pin 19)
    DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
    DDRC &= ~(1<<PC2); // set PC2 as input

// turn on the internal resistor for the pin
    PORTC |= (1<<PC2); // turn on internal pull up resistor

// declare the variables to represent pushbutton input
uint8_t mode_select;

    while(1) {  //main loop
        mode_select = (PINC & (1<<PC2)) >> PC2;

    //here is where we begin the code for our double tap.
        switch(STATE) {
            case STATE_BUTTONFLOAT: {
                if (counterfloater > 2) {
                    counterfloater = 0;
                    } //end counterfloater>2
                else {
                    if (counterfloater <=2) {
                        if (mode_select == 0) {
                            counterfloater = 0; //don't want anything mucking up our memory, so we reset this so we can reuse it later
                            STATE = STATE_FIRSTPRESSED;
                            } //end mode_select == 0
                        else {
                            if (mode_select == 1) {
                                STATE = STATE_BUTTONFLOAT;
                                }//end mode_select == 1
                            }
                        } //end counterfloater <= 2
                    }

                break;
                }

            case STATE_FIRSTPRESSED: {
                if (counterfp <= 72) { //if we are within the time limit
                    if (mode_select == 1) { //and if the button is not being pressed anymore
                        counterfp = 0; //once again, clear the variable so we can use it again and make our memory clear.
                        STATE = STATE_FIRSTRELEASED;
                        }//end mode_select == 1 //change the state of the button to when it is first being released

                    else {
                        if (mode_select ==  0) {//if we are in the time limit and the button is still being pressed
                            counterfp += 1;
                            }//end mode_select == 0 //then the counter goes up by one
                        }
                    } //end counterfp <=72
                else {
                    if (counterfp > 72) { //however, if the user is outside the time limit, no matter the state of the button now...
                        counterfp = 0; //clear variable for other memory use and cleared for later
                        STATE = STATE_BUTTONFLOAT;
                        }//end counterfp > 72
                    /* since they are outside the time limit, we can just assume the user
                            did not press the button for long enough, or that they were just picking something
                            up. This is why we do the double tap, to avoid misfires when we don't actually
                            WANT to fire.*/
                    }
                break;
                }

            case STATE_FIRSTRELEASED: {
                if (counterfr <= 72) { //if we are within the time limit
                    if (mode_select == 0) {//and if the button is being pressed again
                        counterfr = 0; //once again, clear the variable so we can use it again and make our memory clear.
                        STATE = STATE_SECONDPRESS;
                        } //end mode_select == 0 //change the state of the button to when it is being pressed a second time

                    else {
                        if (mode_select ==  1){ //if we are in the time limit and the button is still not pressed
                            counterfr += 1;
                            } //end mode_select == 1 //then the counter goes up by one
                        }
                    } //end counterfr <= 72

                else {
                    if (counterfr > 72) { //however, if the user is outside the time limit, no matter the state of the button now...
                        counterfr = 0; //clear variable for other memory use and cleared for later
                        STATE = STATE_BUTTONFLOAT;
                        }//end counterfr > 72
                            /* since they are outside the time limit, we can just assume the user
                            did not have the button released for long enough, or that they were just picking something
                            up. This is why we do the double tap, to avoid misfires when we don't actually
                            WANT to fire.*/
                    }
                break;
                }

            case STATE_SECONDPRESS: {
                if (countersp > 2) { //if we are outside of the arbitrary time limit
                    countersp = 0;
                    } //end of countersp > 2 //put ourselves back in the arbitrary timer limit
                else {
                    if (countersp <=2) { //if we are in the arbitrary time limit
                        if (mode_select == 0){//and the button is still being pressed here
                            PORTB &= ~(1<<PB5);
                            }//end of mode_select == 0 //output is true.
                        else {
                            if (mode_select == 1){//if we are not pressing the button anymore
                                countersp = 0; //reset our arbitrary second push counter for use later
                                STATE = STATE_BUTTONFLOAT;
                                } //end mode_select == 1 //and return back to floating state, since we're not pressing it anymore.
                            }
                        } // end countersp<=2
                    }

                break;
                }
            }// end of switch

        delay_ms(1);

        }//end of while
    } //end of main

/*well, that's it for the double tap program. There are several ways to do this, but this is just one example that I thought
would be simple and not take up a lot of memory. One other way to do this is by using interrupts, which are actually really
useful when thinking about outside events. However, it takes a lot of setting up and weird variables to make. That is why
I don't use them, and rather set up code that other people can look at and understand why I did it.*/
October 03, 2012
by wadaltmon
wadaltmon's Avatar

Sorry, Jim, but no go. The code didn't compile, getting the same errors as before :( .

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I rewrote the switch statement to no avail, and I've researched the error. Still, I can't find anything wrong with the program. Can someone test this out on their system, to make sure it's not just my computer that's messing it up?

October 03, 2012
by wadaltmon
wadaltmon's Avatar

Tried on another system just now, transferred fine. Redownloaded WinAVR and code transferred OK.

However, when I plug in the power, the LED stays on and nothing happens when the button is pressed. :( Why is the light just staying on?

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

Maybe you did not copy it correctly, or put it in the correct place?

It did compile for me...

    C:\Nerdkits\!DIY-Projects\Rsp-Dalton>make
    make -C ../libnerdkits
    make[1]: Entering directory `C:/Nerdkits/!DIY-Projects/libnerdkits'
    make[1]: Nothing to be done for `all'.
    make[1]: Leaving directory `C:/Nerdkits/!DIY-Projects/libnerdkits'
    avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscan
    f -lscanf_flt -lm -o initialload.o initialload.c ../libnerdkits/delay.o ../libne
    rdkits/lcd.o ../libnerdkits/uart.o
    avr-objcopy -j .text -O ihex initialload.o initialload.hex
    avr-size -A initialload.hex
    initialload.hex  :
    section   size   addr
    .sec1   7954      0
    Total   7954

You should be able to copy what I changed and have it work...

I compiled the code, and I did an assembler dump of the object, it matched the source.

Try this...

1 - Make a copy of your source folder. 2 - Work with the copy only. 3 - Delete .o 4 - Delate .hex 5 - Copy the changes of have into the proper ".c" file. (I am not sure what you have that named as per your "make file". 6 - The compile...\

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

What I find "extremely useful" is:

avr-objdump -t -S -D initialload.o > initialload.asm

What this will do is take an "object file" and create an associated "asm file" that has that also has the source code as remarks .

I have used this numerous times to determine what seems to make programs smaller, as well as for some debugging issues.

(I just noticed I didn't space my instructions from before and they got merged together....

So here it is again...

1 - Make a copy of your source folder. 
2 - Work with the copy only. 
3 - Delete *.o 
4 - Delate *.hex 
5 - Copy the changes of have into the proper ".c" file, and make sure it's saved.
    (I am not sure what you have that named as per your "make file"). 
6 - The compile...

Also here is an example of the asm dump.

int main() {
 1f6:   ef 92           push    r14
 1f8:   ff 92           push    r15
 1fa:   0f 93           push    r16
 1fc:   1f 93           push    r17
 1fe:   cf 93           push    r28
 200:   df 93           push    r29

// Set the output pin (pin 19)
    DDRB |= (1<<PB5);
 202:   25 9a           sbi 0x04, 5 ; 4

// Set the pin to input mode for Pushbutton
    DDRC &= ~(1<<PC2); // set PC2 as input
 204:   3a 98           cbi 0x07, 2 ; 7

// turn on the internal resistor for the pin
    PORTC |= (1<<PC2); // turn on internal pull up resistor
 206:   42 9a           sbi 0x08, 2 ; 8

            case STATE_FIRSTPRESSED: {
                if (counterfp <= 72) { //if we are within the time limit
                    if (mode_select == 1) { //and if the button is not being pressed anymore
                        counterfp = 0; //once again, clear the variable so we can use it again and make our memory clear.
                        STATE = STATE_FIRSTRELEASED;
 208:   92 e0           ldi r25, 0x02   ; 2
 20a:   e9 2e           mov r14, r25
 20c:   f1 2c           mov r15, r1

            case STATE_FIRSTRELEASED: {
                if (counterfr <= 72) { //if we are within the time limit
                    if (mode_select == 0) {//and if the button is being pressed again
                        counterfr = 0; //once again, clear the variable so we can use it again and make our memory clear.
                        STATE = STATE_SECONDPRESS;
 20e:   03 e0           ldi r16, 0x03   ; 3
 210:   10 e0           ldi r17, 0x00   ; 0
                    } //end counterfloater>2
                else {
                    if (counterfloater <=2) {
                        if (mode_select == 0) {
                            counterfloater = 0; //don't want anything mucking up our memory, so we reset this so we can reuse it later
                            STATE = STATE_FIRSTPRESSED;
 212:   c1 e0           ldi r28, 0x01   ; 1
 214:   d0 e0           ldi r29, 0x00   ; 0
October 03, 2012
by wadaltmon
wadaltmon's Avatar

I finally got mine to compile on a different system. However, the output just stays true no matter what :( Any thoughts on that?

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

NOTE: The assembler code is sometimes hard to follow because due to optimizations sometimes source statements are duplicated and sometimes the original order is changed as well.

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

Well getting it to compile is GREAT!

What do you mean the "output stays true"?

I am not sure what you mean by that...

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

You should determine why you were having problems compiling on your initial system.

That should not happen, and may be indicative of some issue that should be resolved...

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I mean, I want the pin PC2 to receive an input which is ground, and then output ground at PB5 while the second press is held. However, it is always putting out ground at PB5, which is definitely not right.

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

Who are you determining PB5 is always a 0? (GND).

Are you measuring with some device or do you have an LED connected that is never changing state?

What I would do first, is to create a program that is very limited use a

delay_ms(2000)

(code you are using to send a 0)

delay_ms(2000)

(code you are using to send a 1)

If that is in a "while" statement that never ends it should just repeat constantly....

If that works, you see and alternating 0 and 1 or blinking LED, then we can go from there...

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

(I meant "How", not "Who")

October 03, 2012
by wadaltmon
wadaltmon's Avatar

I've an LED on it that is never changing. I verified it with a multimeter, and built that program. It seems to work right, blinking LED. So...

October 03, 2012
by JimFrederickson
JimFrederickson's Avatar

It would be good to test it now too, just to make sure...

Yes, but sometimes there can be loose wires.

If you test it now how it sits, without moving it around, just to verify it is working still as you expect that is a good test.

You didn't answer before, or I didn't notice, did you implement a hardware "debounce"?

I will look at the logic of the program overnight/morning.

I didn't look at the logic before, I only make changes to make sure it would compile.

The logic is sort-of dependent of what you have done too. (Hardware debounce for instance...)

October 03, 2012
by wadaltmon
wadaltmon's Avatar

Hardware debounce was implemented, but even after I disconnected the debounce circuit completely, the light was still on. If I restarted the program with no switch connected or anything, the light was still on.

Thanks for looking at it, it really means a lot. Thanks :)

October 04, 2012
by pcbolt
pcbolt's Avatar

Dalton -

I went through your code and it looks OK (it can be trimmed quite a bit but that is for another day). What is a possible concern might be your time limit of 72. If you delay each cycle by 1 mSec, 72 mSec (plus negligible code execution time) is very fast for "human" time. As a gauge, I think of runners at a starting block. If they release from the block in less than 100 mSec after hearing the starting gun, they basically "jumped the gun" and got a false start. Try bumping up your delay time to 2 mSec and see if that helps.

October 04, 2012
by pcbolt
pcbolt's Avatar

Dalton -

Just thought of something else. To make sure your code is getting executed properly, you could try adding in some more output LED's to see if the intermediate steps are getting registered. This would not be part of the final project but for debugging you could have a separate LED light up for each "case" statement. Of course your O-scope should be able to eliminate some of these problems and the timing issue I mentioned in the last post.

October 04, 2012
by wadaltmon
wadaltmon's Avatar

Well, yeah... my scope should definitely tell me if the button is debounced or not, and if it is pausing before hitting the button.

So, when I keep adding one to a variable, it actually takes just 1 millisecond? That's pretty useful. However, I was actually trying to go for a limit of 0.5 seconds... so if that is true, I should go for a time limit of 500... because 500 milliseconds is half of one second.

And what does the time delay at the end actually do? I never really understood.

October 04, 2012
by wadaltmon
wadaltmon's Avatar

@JimFrederickson "What I would do, if you are not going to use interrupts, is to just put a "delay_ms (1)" as the last statement in your "while". Then right after that update any "clock tick", "clock counter", or "clock count down counters" you will have to create the delays/timers you want/need"

What do you mean by update the clock? Or is this just if at some point I will update to interrupts?

October 04, 2012
by wadaltmon
wadaltmon's Avatar

Alright I now understand what the adding of the delay does. I guess it makes the delay time of each cycle of adding the variable longer or shorter.

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

I noticed a whole flurry of new messages, has the lite begun to work, or is it still not working?

(I am assuming it is still not working...)

So this:

PORTB &= ~(1<<PB5);

is what you say will turn the LED on?

Does this cause the LED to flash every 2 seconds?

while(1) {  //main loop

    delay_ms(2000);

    PORTB &= ~(1<<PB5);

    delay_ms(2000);

    PORTB |= (1<<PB5);
    }

Here is another form of this that will make the "delay_ms(1)" and what I said about counters before make sense...

uint8_t     mode_select1;
uint16_t    flashcounter1;

mode_select1 = 0;
flashcounter1 = 1000;

while(1) {  //main loop
    if (flashcounter1 == 0) {
        flashcounter1 = 1000;
        if (mode_select1 == 0) {
            mode_select1 = 1;
            PORTB &= ~(1<<PB5);
            }
        else {
            mode_select1 = 0;
            PORTB |= (1<<PB5);  
            }
        }

    delay_ms(1);

    if (fashcounter1 != 0) {
        flashcounter1--;
        }
    }

Why do you think I use:

    if (fashcounter1 != 0) {
        flashcounter1--;
        }

It probably seems a bit "pointless" to you at first?

Notice since we no longer are using the "delay_ms(2000)" we have an extra 50,000+ cycles to use. (Here of course that doesn't matter, but in a more complex program/environment it can.)

Do both of these work?

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

In a slightly more complex example, to give you an idea...

uint8_t     mode_select1;
uint16_t    flashcounter1;

uint8_t     mode_select2;
uint16_t    flashcounter2;

mode_select1 = 0 ;
flashcounter1 = 1000;

mode_select2 = 0;
flashcounter2 = 1200;

while(1) {  //main loop

    if (flashcounter1 == 0) {
        flashcounter1 = 1000;
        if (mode_select1 == 0) {
            mode_select1 = 1;
            PORTB &= ~(1<<PB5);
            }
        else {
            mode_select2 = 0;
            PORTB |= (1<<PB5);  
            }
        }

    if (flashcounter2 == 0) {
        flashcounter2 = 1200;
        if (mode_select2 == 0) {
            mode_select2 = 1;
            PORTB &= ~(1<<PB4);
            }
        else {
            mode_select2 = 0;
            PORTB |= (1<<PB4);  
            }
        }

    delay_ms(1);

    if (flashcounter1 != 0) {
        flashcounter1--;
        }

    if (flashcounter2 != 0) {
        flashcounter2--;
        }

    }

If you add another LED, providing the first one is working as wired, 2 should have 2 LEDS flashing at different rates. Everything 5th time through the flashing cycle their flashing should correspond.

The reason for the coding of: (aside from the fact that a "timeout counter" should not decrement below 0.) if (flashcounter1 != 0) { flashcounter1--; }

Is "communications"...

"Communications" not necessary in this example, but if you took out the "delay_ms(1)" and replaced it with an "interrupt time base" and moved your counter updating to the "interrupt function" "communications" is important.

When the "interrupt occurs" you do not have any idea where you will be in your "main code".

So you have no idea if the "main code" has noticed the "flashcounter1" timeout.

If the "interrupt code" checks to see if the timeout has been set to some value, and you only set that value when you have noticed there was a timeout then the "interrupt code" and the "main code" will be insync.

No timeouts will be missed.

October 04, 2012
by wadaltmon
wadaltmon's Avatar

Yes, the examples you provided work for the flashing of LEDs (after fixing a few spelling mistakes within, but that took a few seconds).

So, the problem with the program, the reason why the LED stays on, is just timing?

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

I had thought I fixed those mispellings???

Maybe I got confused on what I had typed/copied and assembled...

The button press is a GND condition in your circuit, right?

Notice we have a "button1_state" which is the current instantaneous state of the button, but "button1_pressed" is only set after some multiple occurrences of the button state being pressed. I have had some "high vibration situations" where the button state changed quite a bit. Sometimes I have incremented the debounce count by 10 and so that very brief momentary off conditions did not have as much of an affect.

The choice of 7 is not magical it is pretty arbitrary. Maybe a little higher, or lower, would work better or as well.

I would also, normally, separate out the button processing in a separate function, but this is a short program.

//  This should turn the LED on and keep it on for 1 second after the button is released
    uint8_t     mode_select1;
    uint16_t    flashcounter1;

    uint8_t     button1_state;
    uint8_t     button1_debounce_count;
    uint8_t     button1_pressed;

    mode_select1 = 0;
    flashcounter1 = 0;

    button1_debounce_count = 0;
    button1_pressed = 0;

    while(1) {  //main loop

        button1_state = (PINC & (1<<PC2)) >> PC2;

//  Is the button being pressed
        if (button1_state == 0) {
            button1_debounce_count++;           //  Update our debounce count
            if (button1_debounce_count > 7) {   //  Once out debounce count > 7 we accept the button is being pressed
                button1_debounce_count = 7;     //  Here we are just making sure we don't go beyound our Max debounce count
                button1_pressed = 1;            //  Set button pressed state
                }
            }
        else {
            if (button1_debounce_count != 0) {
                button1_debounce_count--;       //  Our button is not pressed reduce debounce count
                }
            else {
                button1_pressed = 0;            //  Our becounce count is at 0 so the button is not being pressed
                }
            }

//  Do we have a valid button press
        if (button1_pressed == 1) {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            }

//  If our counter has timed out and the mode is LED on then we need to change the mode and turn the LED off
        if ((flashcounter1 == 0) &&
            (mode_select1 == 1)) {
            mode_select1 = 0;
            PORTB |= (1<<PB4);  
            }

        delay_ms(1);

        if (flashcounter1 != 0) {
            flashcounter1--;
            }

        }

I know that in C I can write:

    if (button1_pressed) {

But I thought it was more obvious/exact if I wrote it out completely.

This should check the rest of the hardware, which is only the switch. (Unless I have a dreaded logic problem, which I really hate when posting! :) )

Does this work?

October 04, 2012
by wadaltmon
wadaltmon's Avatar

I inserted our example into the code thus:

#define F_CPU 14745600

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"

//  This should turn the LED on and keep it on for 1 second after the button is released

    int button1_state = 1;

    int mode_select1 = 0;
    int flashcounter1 = 0;

    int button1_debounce_count = 0;
    int button1_pressed = 0;

void main(){    
    // Set the output pin (pin 19)
  DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
  DDRC &= ~(1<<PC2); // set PC2 as input

  // turn on the internal resistor for the pin
  PORTC |= (1<<PC2); // turn on internal pull up resistor

    while(1) {  //main loop

        button1_state = (PINC & (1<<PC2)) >> PC2;

//  Is the button being pressed
        if (button1_state == 0) {
            button1_debounce_count++;           //  Update our debounce count
            if (button1_debounce_count > 7) {   //  Once out debounce count > 7 we accept the button is being pressed
                button1_debounce_count = 7;     //  Here we are just making sure we don't go beyound our Max debounce count
                button1_pressed = 1;            //  Set button pressed state
                }
            }
        else {
            if (button1_debounce_count != 0) {
                button1_debounce_count--;       //  Our button is not pressed reduce debounce count
                }
            else {
                button1_pressed = 0;            //  Our becounce count is at 0 so the button is not being pressed
                }
            }

//  Do we have a valid button press
        if (button1_pressed == 1) {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            }

//  If our counter has timed out and the mode is LED on then we need to change the mode and turn the LED off
        if ((flashcounter1 == 0) &&
            (mode_select1 == 1)) {
            mode_select1 = 0;
            PORTB |= (1<<PB5); 
            }

        delay_ms(1);

        if (flashcounter1 != 0) {
            flashcounter1--;
            }

        }}

And yet, it still leaves the LED on. Strange, isn't it?

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

So you are saying that when you first power up your circuit the LED is on

And whether you press the button, or not, it stays on

Right?

But the "blinking examples for the LED" all worked?

Right?

October 04, 2012
by wadaltmon
wadaltmon's Avatar

Yes, that is what I am saying.

Actually, the LED blink code is strange... It turns the light on for a second, then flashes rapidly a lot, then turns off again for a really long time. Then it does the same thing again about a minute later, flashing randomly.

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

Both versions of the blink code have inconsistent blinking, or just one?

(The one using the "delay_ms(2000)" vs the "delay_ms(1) and counter"?)

I am moving around right now. (Physically...)

I will be able to put my hardware together on Saturday.

I will try the programs I posted to see what they do on my hardware on Saturday. (Maybe Friday Night, but Saturday is the safe-bet day...)

The only testing I was able to do with what I had posted already was just to see if it compiled. I may have some issue in the code.

The code you are using to read the pin:

button1_state = (PINC & (1<<PC2)) >> PC2;

Is different than what I use but it seems, just looking at it, like it should work...

    button1_state = (PINC & 0x04);

That is closer to the code I would normally use to read a switch. (I put the "~" operator in to get a value for being pressed, but since we are already working on press being 0 that doesn't fit here. The mask, "0x04" is just to isolate that pin. PC0 mask is 0x01, PC1 mask is 0x02, PC2 mask is 0x04, etc...)

You may want to try that to see if that makes any difference?

The "data direction selection" looks good, the "pull-up resister" looks good.

You are sure the button you are using is "normally open" button that you press to close? One pin is tied to GND and the other to the Microcontroller input pin?

October 04, 2012
by Noter
Noter's Avatar

I tried the most recent version posted and it works. The LED is on after reset because logical zero is the output pin's default state. Press the button anyway and the LED goes off after about a second and then works as expected thereafter. If you don't want the LED on in the beginning just turn it off before the while loop.

// turn it off
PORTB |= (1<<PB5);

while ...

That is an interesting and simple way to debounce a button, I like it.

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

Thanks Noter...

Glad to hear my code worked (I am ALWAYS concerned when I can't test it before posting...)

Dalton...

I guess that leaves something with your circuit.

Loose wires?

Maybe the wrong gauge for the proto-board? Maybe a large gauge wire was put in the proto-board before and loosened the socket?

Had you removed your Hardware debounce when you tried it?

Since Noter says it works, I will post the actual "doubletap" code in about 1 hr.

October 04, 2012
by JimFrederickson
JimFrederickson's Avatar

If you are running on "battery" and not an Power Source a low battery can cause LED flicker too!

(Not usually the cause, but I have run into situations where that can happen.)

October 05, 2012
by JimFrederickson
JimFrederickson's Avatar

Hi Dalton,

Actually I did notice that the code I posted did have a error.

I had PB4 set to turn the LED off, it should have been PB5.

You corrected that in the version you posted, but maybe you didn't recompile after you fixed my error.

(Or maybe there is still some issue with your circuit.)

Noter said the last version ran, which is what you posted.

(So that was after my mistake was fixed.)

Since we are done "testing" and, I think, know that the hardware does work, or at least can work, the program does turn off the LED to start now.

Once the program read a "doubletap" it should turn the LED on for 1 second.

Here is the rest of the "doubletap" part...

NOTE: I did redefine the "counterfloater". The reason being because I ALWAYS explicitly define my size, and really don't remember anymore what size "int" is by default.

//  This should turn the LED on and keep it on for 1 second after the button is released
    uint16_t    counterfloater;

    uint8_t     mode_select1;
    uint16_t    flashcounter1;

    uint8_t     button1_state;
    uint8_t     button1_debounce_count;
    uint8_t     button1_pressed;

    counterfloater = 0;

    mode_select1 = 0;
    flashcounter1 = 0;

    button1_debounce_count = 0;
    button1_pressed = 0;

    STATE = STATE_BUTTONFLOAT;

    PORTB |= (1<<PB5);                  //  Turn Off the LED

    while(1) {  //main loop

        button1_state = (PINC & (1<<PC2)) >> PC2;

//  Is the button being pressed
        if (button1_state == 0) {
            button1_debounce_count++;           //  Update our debounce count
            if (button1_debounce_count > 7) {   //  Once out debounce count > 7 we accept the button is being pressed
                button1_debounce_count = 7;     //  Here we are just making sure we don't go beyound our Max debounce count
                button1_pressed = 1;            //  Set button pressed state
                }
            }
        else {
            if (button1_debounce_count != 0) {
                button1_debounce_count--;       //  Our button is not pressed reduce debounce count
                }
            else {
                button1_pressed = 0;            //  Our becounce count is at 0 so the button is not being pressed
                }
            }
/*          
//  Do we have a valid button press
        if (button1_pressed == 1) {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            }
*/      
//  If our counter has timed out and the mode is LED on then we need to change the mode and turn the LED off
        if ((flashcounter1 == 0) &&
            (mode_select1 == 1)) {
            mode_select1 = 0;
            PORTB |= (1<<PB5);                  //  Turn Off the LED
            }

//  here is where we begin the code for our double tap
//  Now we can do the "doubletap".
//  The button has already been debounced so we don't need to worry about that

    switch(STATE) {

//  For the "STATE_BUTTONFLOAT" we are waiting for a button press to begin

       case STATE_BUTTONFLOAT: {
            if (button1_pressed == 1) {
                counterfloater = 2000;          //  We will wait 2 seconds for a button release and repeat press "doubletap"
                STATE = STATE_FIRSTPRESSED;
                } 
            break;
            }

//  Now the button has been pressed once we are waiting for a release or timeout
        case STATE_FIRSTPRESSED: {
            if (button1_pressed == 1) {
                if (counterfloater == 0) {
                    STATE = STATE_BUTTONFLOAT;  //  Button is still pressed and we have timed out - restart the process
                    }
                }
            else {
                STATE = STATE_FIRSTRELEASED;    //  The button has been released so now we are waiting for a second press
                }
            break;
            }

        case STATE_FIRSTRELEASED: {
            if (button1_pressed == 1) {
                STATE = STATE_SECONDPRESS;      //  We have been pressed again
                }
            else {
                if  (counterfloater == 0) {
                    STATE = STATE_BUTTONFLOAT;  // Button has not been pressed after the first release and we have timed out - restart the process
                    }
                }
            break;
            }

        case STATE_SECONDPRESS: {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            STATE = STATE_BUTTONFLOAT;          //  LED is turned on, waiting to timeout - we restart the process

            break;
            }
        }// end of switch

        delay_ms(1);

        if (flashcounter1 != 0) {
            flashcounter1--;
            }

        if (counterfloater != 0) {
            counterfloater--;
            }

        }
October 05, 2012
by JimFrederickson
JimFrederickson's Avatar

Hopefully, I didn't have any errors in that code!

Noter? :)

It turns out I may be busy on Saturday/Sunday, so I still may not be unpacked! I will know more tomorrow, well later today, evening...

Things that I would do now...

1 - "STATE" I would change to "state_doubletap"...  
    For a simple program it is not really an issue
    but if you have a program with multiple things running 
    I find it helpful to differentiate them.

    Whatever it is called it should be lower case though
    since upper case is by convention for "CONSTANTS".

2 - While the "delay_ms(1)" and the counters, and using states do get you to
    a point where you can utilize the Microcontroller more
    effectively than just using a "delay_ms(1000)", you don't
    get as consistent of a timed base as you could.

    Because we have "code" that is executing and using up 
    cycles, then we are using a "delay_ms(1)" to burn more cycles
    our time base is not really 1msec.

    Our time base is 1msec+(cycles for every instruction ran before the delay_ms(1)).

    Since the code is small, at least now, the difference
    is not that great.

    However if you were trying to keep track of actual time, or trying
    measure the duration of some event you would be off.

    This is just the easiest way, that I know, to get as close as possible
    without using interrupts.

    To get a better time base you will need to use interrupts.

    A single timer interrupt that just updates the counters,
    and a tickcounter which I find useful, is all you would need.

3 - Speaking of "counters".
    What I would normally do is to put them ALL into an 
    array.

    That way they can be updated without alot of coding in 
    a loop.

    I then use "CONSTANTS" to differentiate which ones I use where.

4 - Whenever I have more than a single button I put them into an
    array as well.

    Then I can use a "switch statement" to execute code to read 
    the state of individual buttons.  (Since normally they are on
    I/O pins.

    You can get alot more efficient in the debounce by merging 
    "button pressed" with the unused bits in the "debounce count".
    I just like to stay more simple.  (when possible.)

I hope this helps, and that you can see some useful ideas in this exercise.

Did it work for you?

October 05, 2012
by Noter
Noter's Avatar

This version needed a bit of fine tuning but otherwise is in good shape. Other than declaring variables and values I added a new state to wait for the button to be released after the second press before starting over.

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "../libnerdkits/lcd.h"  
#include "../libnerdkits/uart.h"
#include "../libnerdkits/delay.h"

    int button1_state = 1;

    int mode_select1 = 0;
    int flashcounter1 = 0;

    int button1_debounce_count = 0;
    int button1_pressed = 0;

int main(){    
    // Set the output pin (pin 19)
  DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
  DDRC &= ~(1<<PC2); // set PC2 as input

  // turn on the internal resistor for the pin
  PORTC |= (1<<PC2); // turn on internal pull up resistor

//  This should turn the LED on and keep it on for 1 second after the button is released
    uint16_t    counterfloater;

    uint8_t     mode_select1;
    uint16_t    flashcounter1;

    uint8_t     button1_state;
    uint8_t     button1_debounce_count;
    uint8_t     button1_pressed;

    counterfloater = 0;

    mode_select1 = 0;
    flashcounter1 = 0;

    button1_debounce_count = 0;
    button1_pressed = 0;

#define STATE_BUTTONFLOAT           1
#define STATE_FIRSTPRESSED      2
#define STATE_FIRSTRELEASED     3
#define STATE_SECONDPRESS           4

#define STATE_WAIT                      5

uint8_t state_doubletap;

    state_doubletap = STATE_WAIT;

    PORTB |= (1<<PB5);                  //  Turn Off the LED

    while(1) {  //main loop

        button1_state = (PINC & (1<<PC2)) >> PC2;

//  Is the button being pressed
        if (button1_state == 0) {
            button1_debounce_count++;           //  Update our debounce count
            if (button1_debounce_count > 7) {   //  Once out debounce count > 7 we accept the button is being pressed
                button1_debounce_count = 7;     //  Here we are just making sure we don't go beyound our Max debounce count
                button1_pressed = 1;            //  Set button pressed state
                }
            }
        else {
            if (button1_debounce_count != 0) {
                button1_debounce_count--;       //  Our button is not pressed reduce debounce count
                }
            else {
                button1_pressed = 0;            //  Our becounce count is at 0 so the button is not being pressed
                }
            }
/*          
//  Do we have a valid button press
        if (button1_pressed == 1) {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            }
*/      
//  If our counter has timed out and the mode is LED on then we need to change the mode and turn the LED off
        if ((flashcounter1 == 0) &&
            (mode_select1 == 1)) {
            mode_select1 = 0;
            PORTB |= (1<<PB5);                  //  Turn Off the LED
            }

//  here is where we begin the code for our double tap
//  Now we can do the "doubletap".
//  The button has already been debounced so we don't need to worry about that

    switch(state_doubletap) {

//  For the "STATE_BUTTONFLOAT" we are waiting for a button press to begin

       case STATE_WAIT: {
            if (button1_pressed == 0) {                 // Let's be sure the button is not pressed already before we begin
                state_doubletap = STATE_BUTTONFLOAT;
                } 
            break;
            }

       case STATE_BUTTONFLOAT: {
            if (button1_pressed == 1) {
                counterfloater = 2000;          //  We will wait 2 seconds for a button release and repeat press "doubletap"
                state_doubletap = STATE_FIRSTPRESSED;
                } 
            break;
            }

//  Now the button has been pressed once we are waiting for a release or timeout
        case STATE_FIRSTPRESSED: {
            if (button1_pressed == 1) {
                if (counterfloater == 0) {
                    state_doubletap = STATE_BUTTONFLOAT;  //  Button is still pressed and we have timed out - restart the process
                    }
                }
            else {
                state_doubletap = STATE_FIRSTRELEASED;    //  The button has been released so now we are waiting for a second press
                }
            break;
            }

        case STATE_FIRSTRELEASED: {
            if (button1_pressed == 1) {
                state_doubletap = STATE_SECONDPRESS;      //  We have been pressed again
                }
            else {
                if  (counterfloater == 0) {
                    state_doubletap = STATE_BUTTONFLOAT;  // Button has not been pressed after the first release and we have timed out - restart the process
                    }
                }
            break;
            }

        case STATE_SECONDPRESS: {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            state_doubletap = STATE_WAIT;          //  LED is turned on, waiting to timeout - we restart the process

            break;
            }
        }// end of switch

        delay_ms(1);

        if (flashcounter1 != 0) {
            flashcounter1--;
            }

        if (counterfloater != 0) {
            counterfloater--;
            }

        }

}
October 05, 2012
by wadaltmon
wadaltmon's Avatar

So the above version should turn on the LED for one second after the button is double tapped, right?

October 05, 2012
by JimFrederickson
JimFrederickson's Avatar

Yes, that is what it should do.

Noter added the:

   case STATE_WAIT: {

Which I didn't have, but it is a good idea just to make sure thata multiple "doubletap" is treated completely at 2 separate events rather than being tainted from the end on a previous "doubletap".

The "doubletap" must occur in a 2 second window, as timed by the "counterfloater". Changing that value will change the length of the window.

If you are using states in a program you have to make sure you "don't get stuck"... It is easy to do where it gets stuck in one state and doesn't progress or start over.

I didn't mention before, in the things I would change, that normally I would group all of the "time out values" together as constants. That way if you have a long program you only need to change one place to adjust your timing.

For this code there are essentially 3 processes that are taking place.

1 - Checking the button state and debouncing/validating the button press and release.

2 - Keeping an LED on for a time period.

3 - Validating the "doubletap" event.

October 05, 2012
by wadaltmon
wadaltmon's Avatar

HEY! THE code completely works! It works to turn on the light after the double tap! The base is done, now all that I need to do is make sure that the LED stays on for the entire time while I press the button!

You guys are all awesome! Thank you so much for this! I swear, I will not forget this.

Here is the final code for the original task:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "../libnerdkits/lcd.h" 
#include "../libnerdkits/uart.h"
#include "../libnerdkits/delay.h"

    int button1_state = 1;

    int mode_select1 = 0;
    int flashcounter1 = 0;

    int button1_debounce_count = 0;
    int button1_pressed = 0;

int main(){   
    // Set the output pin (pin 19)
  DDRB |= (1<<PB5);

// Set the pin to input mode for Pushbutton
  DDRC &= ~(1<<PC2); // set PC2 as input

  // turn on the internal resistor for the pin
  PORTC |= (1<<PC2); // turn on internal pull up resistor

//  This should turn the LED on and keep it on for 1 second after the button is released
    uint16_t    counterfloater;

    uint8_t     mode_select1;
    uint16_t    flashcounter1;

    uint8_t     button1_state;
    uint8_t     button1_debounce_count;
    uint8_t     button1_pressed;

    counterfloater = 0;

    mode_select1 = 0;
    flashcounter1 = 0;

    button1_debounce_count = 0;
    button1_pressed = 0;

#define STATE_BUTTONFLOAT           1
#define STATE_FIRSTPRESSED      2
#define STATE_FIRSTRELEASED     3
#define STATE_SECONDPRESS           4

#define STATE_WAIT                      5

uint8_t state_doubletap;

    state_doubletap = STATE_WAIT;

    PORTB |= (1<<PB5);                  //  Turn Off the LED

    while(1) {  //main loop

        button1_state = (PINC & (1<<PC2)) >> PC2;

//  Is the button being pressed
        if (button1_state == 0) {
            button1_debounce_count++;           //  Update our debounce count
            if (button1_debounce_count > 7) {   //  Once out debounce count > 7 we accept the button is being pressed
                button1_debounce_count = 7;     //  Here we are just making sure we don't go beyound our Max debounce count
                button1_pressed = 1;            //  Set button pressed state
                }
            }
        else {
            if (button1_debounce_count != 0) {
                button1_debounce_count--;       //  Our button is not pressed reduce debounce count
                }
            else {
                button1_pressed = 0;            //  Our becounce count is at 0 so the button is not being pressed
                }
            }
/*         
//  Do we have a valid button press
        if (button1_pressed == 1) {
            mode_select1 = 1;                   //  Set our mode to LED on
            flashcounter1 = 1000;               //  Set out timeout to 1,000 msec
            PORTB &= ~(1<<PB5);             //  Turn on the lead
            }
*/

//  here is where we begin the code for our double tap
//  Now we can do the "doubletap".
//  The button has already been debounced so we don't need to worry about that

    switch(state_doubletap) {

//  For the "STATE_BUTTONFLOAT" we are waiting for a button press to begin

       case STATE_WAIT: {
            if (button1_pressed == 0) {                 // Let's be sure the button is not pressed already before we begin
                state_doubletap = STATE_BUTTONFLOAT;
                }
            break;
            }

       case STATE_BUTTONFLOAT: {
            if (button1_pressed == 1) {
                counterfloater = 500;          //  We will wait 1/2 seconds for a button release and repeat press "doubletap"
                state_doubletap = STATE_FIRSTPRESSED;
                }
            break;
            }

//  Now the button has been pressed once we are waiting for a release or timeout
        case STATE_FIRSTPRESSED: {
            if (button1_pressed == 1) {
                if (counterfloater == 0) {
                    state_doubletap = STATE_BUTTONFLOAT;  //  Button is still pressed and we have timed out - restart the process
                    }
                }
            else {
                state_doubletap = STATE_FIRSTRELEASED;    //  The button has been released so now we are waiting for a second press
                }
            break;
            }

        case STATE_FIRSTRELEASED: {
            if (button1_pressed == 1) {
                state_doubletap = STATE_SECONDPRESS;      //  We have been pressed again
                }
            else {
                if  (counterfloater == 0) {
                    state_doubletap = STATE_BUTTONFLOAT;  // Button has not been pressed after the first release and we have timed out - restart the process
                    }
                }
            break;
            }

        case STATE_SECONDPRESS: {
            mode_select1 = 1;                   //  Set our mode to LED on
            PORTB &= ~(1<<PB5);             //  Turn on the led
            if (button1_pressed == 1) {     //if we are still pressing the button
                mode_select1 = 1;                   //  keep everything as it was
                PORTB &= ~(1<<PB5);}
            else {                          //otherwise, button is not pressed
            PORTB |= (1<<PB5);
            state_doubletap = STATE_WAIT;}          //we restart the process

            break;
            }
        }// end of switch

        delay_ms(1);

        if (flashcounter1 != 0) {
            flashcounter1--;
            }

        if (counterfloater != 0) {
            counterfloater--;
            }

        }

} //that's it for the double tap. Thanks to JimFrederickson, Noter, pcbolt, Ralphxyz for all the help! You guys are awesome!
October 05, 2012
by Ralphxyz
Ralphxyz's Avatar

Now wadaltmon, time for my friendly exhortation to add your project to the Nerdkits Community Library, well maybe when you get your whole project running. But just this double tap routine (which took the community to put together) would be a great addition. If nothing else there should be a link to this thread.

Ralph

October 05, 2012
by wadaltmon
wadaltmon's Avatar

Yeah, definitely put it up there! I hope everyone else can benefit from it as much as I have.

How would I add it there? Or were you going to do it?

Once I get the rest of the project done (by the end of this year, or earlier, maybe) then I will post it in the project ideas forum. But until then, feel free to place this forum in there, if you can. If you can't (I don't know, perhaps there is a lock so that only the original poster can put it up), then just tell me and I'll put it in there.

I'm gonna take some time looking through that library, I had never actually looked through it before!

October 05, 2012
by Ralphxyz
Ralphxyz's Avatar

I like to have the original poster post their projects to the library. If they do not get posted after three months or so I might at least post a link to the forum thread.

Once you sign in to the library you have the ability to edit existing pages.

The best way to learn how to post a new page is to open up a existing page in edit mode so that you can see how it was built and then just replicate that adding new link(s) to your page(s).

Let me know if you have questions.

The library uses the same stupid Markdown syntax as this forum.

Ralph

October 05, 2012
by wadaltmon
wadaltmon's Avatar

I'm probably going to want to finish my project completely before I put it up on there. However, I am documenting it separately from here, so then I can use that to actually make the project writeup. However, I am going to put just the code into the code section, so that everyone can look at it whenever.

October 05, 2012
by wadaltmon
wadaltmon's Avatar

I put the code into the Code Library Section.

October 06, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks wadaltmon, nice writeup.

Ralph

Post a Reply

Please log in to post a reply.

Did you know that a piezoelectric buzzer can be used in reverse as a microphone? Learn more...