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 » Problem having button push polling switch modes properly

December 07, 2012
by adosch
adosch's Avatar

I'm having a slight logical dilemma with some code I am working with to try and make an LED lamp that switches lighting modes (low, bright, fade) with a momentary push button. Right now I'm using infinite loop polling just to rough out the logic, but I can't see past my error.

For whatever reason, I'm fighting with the logic that happens on button push to change the mode. I've tried making my 3-bit shifting logic (2^1 was mode-1, 2^2 was mode-2 and 2^3 was mode 3) or by setting 3 directives and just switching between.

I'm using an attiny2313; PB2 and PB3 have the two LEDs, and PD6 is where the push-button.

Posted below is my simplified logic; I was fooling around to handle the button debounce better, but I've it out for simplicity sake:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

#define MODE1 1
#define MODE2 2
#define MODE3 3

// Initialize micocontroller settings
void init_avr(void) {

   DDRB |= (1 << PB2);
   DDRB |= (1 << PB3);
   DDRD &= ~(1 << PD6);

   // Turn on the internal resistors for the pins for PB1, PB2 and PB3
   PORTD |= (1<<PD6);
   PORTB |= (1<<PB2);
   PORTB |= (1<<PB3);

   // Initial PWM for PB2
   TCCR0A = 0b10000011; // Fast PWM 8 Bit
   TCCR0B = 0b00000001; // No Prescaler

   // Initial PWM for PB3
   TCCR1A = 0b10000011; // Fast PWM 8 bit
   TCCR1B = 0b00000001; // No Prescaler

   TCNT1 = 0; //Reset TCNT1 for PB3
   TCNT0 = 0; //Reset TCNT0 for PB2
}

int switch_mode(uint8_t currmode) {
   if ( currmode == MODE1 ) {
     return MODE2;
   } else if ( currmode == MODE2 ) {
     return MODE3;
   } else if ( currmode == MODE2 ) {
      return MODE1;
   }

   return -1;
}

void do_mode1(void) {
   OCR0A=25; //do bitwise here instead of integer
   OCR1A=25; //do bitwise here instead of integer
}

void do_mode2(void) {
   OCR0A=255; //do bitwise here instead of integer
   OCR1A=255; //do bitwise here instead of integer
}

void do_mode3(void) {
   uint8_t pwm = 0;

   while (pwm <= 255) {
      OCR0A=pwm++;
      OCR1A=pwm++;
      _delay_ms(25);    // Delay 2 millisecond
   }

   pwm=255;

   _delay_ms(40);

   while (pwm >= 0) {
      OCR0A=pwm--;
      OCR1A=pwm--;
      _delay_ms(25);    // Delay 2 millisecond
   }

   _delay_ms(150);    // Delay 100 millisecond
}

void run_mode(uint8_t mode) {
   if (mode == MODE1) {
      do_mode1();
   } else if (mode == MODE2) {
      do_mode2();
   } else if (mode == MODE3) {
      do_mode3();
   } else {
      do_mode1();
   }
}

int main(void) {

   uint8_t mode = MODE1;

   init_avr();

   while(1) {
      if (bit_is_clear(PIND, 6)) {
         mode = switch_mode(mode);
         run_mode(mode);
      }
   }
}
December 07, 2012
by adosch
adosch's Avatar

... (continued)I should have noted what issues I am experiencing (my apologies). After I write my flash my image to the AVR, mode-1 doesn't show at the brightness at it should that I have set in do_mode1(). Then when I push the button to rotate through the modes, I go straight to mode-3 and it's infinitely suck there, I get no more functionality out of the push-button.

December 07, 2012
by JimFrederickson
JimFrederickson's Avatar

There are 2 problems that are stopping your program from working how you want it to.

1 - In the function "switch_mode" there is no wrap around back to mode1. (Note the last if statement you are using there...)

2 - you are looking for the "button being pressed", but that is only 1/2 of the event that you are looking for. Since you are NOT looking for the button to be released it just continuously processes a "button being pressed event". What you really want to be doing is to "process a button pressed event" and then you want to "wait for the button release event" before you process the next "button pressed event"...

Those are the 2 initial problems that need to be remedied.

I don't use the built-in PWM stuff for anything I do, but what you are doing there doesn't look quite right to me...

I am also NOT a fan of using the "delay_ms()" function quite so rampantly. Using that function with large values creates "program stutters" and also limits how you should be viewing programs. (Programs should be viewed as a series of conditions, events, and actions...)

If you go to the library and look for "Safety double tap routine with pushbutton" that should provide some additional insight.

If you want to look at the whole discussion then look at this link:
double tap discussion
(The whole discussion would explain alot to you I think...)

December 09, 2012
by adosch
adosch's Avatar

JimFrederickson,

Thanks for the reply. That code block from 33-43 (e.g. switch_mode() ) was a typo; I was initially testing just MODE1 and MODE2 and forgot to change that back when adding my source code out here. So yes, I did have a wrap back to MODE1.

As for the PWM portions, it looks pretty good to me from the datasheet point of view on how to setup and use. Raising and lowering values from OCR[10]A to adjust the forward voltage over the LED to adjust the 'brightness' level works well.

I did find my problem, which is exactly what you described as 'stuttering' --- when I isolated my modes to static ORC[01]A values only (not using that loop business in do_mode3() ) in all three modes, I saw it. Only when I decided to take that PWM loop-over I was doing did I finally figure out what was happening; it was very unstable and jerky when trying to find that button switch while polling.

After reading through that 'double tap discussion', I drew a personal conclusion that polling is quite the hack for button press-down/up; I spent that latter half of the afternoon trying to work polling logic and I found it much easier to set an external interrupt request on INT0 (PD2) and look for the rising/falling edge from that pin change (as opposed to using PCINT and not having that functionality). The actual switching between my LED 'brightness' modes isn't as 'smooth' as I want to, but it's MUCH improved now using an interrupt. It seems I have to have the button pressed in for ~0.5-1sec to get it to be smooth change from mode to mode. I'll work on that.

Below is a snip from my new code I have now:

SIGNAL (SIG_INT0) {
   mode = switch_mode(mode);
   run_mode(mode);
}

// Initialize micocontroller settings
void init_avr(void) {
   DDRB |= (1 << PB2);
   DDRB |= (1 << PB3);
   DDRD &= ~(1 << PD2);

   PORTD |= (1<<PD2);
   PORTB |= (1<<PB2);
   PORTB |= (1<<PB3);

   // Initial PWM
   TCCR0A = 0b10000011; // Fast PWM 8 Bit
   TCCR0B = 0b00000001; // No Prescaler

   // Initial PWM
   TCCR1A = 0b10000011; // Fast PWM 8 bit
   TCCR1B = 0b00000001; // No Prescaler

   TCNT1 = 0;
   TCNT0 = 0;           // Reset TCNT0
   // Set Pin 6 (PD2) as the pin to use for this example
   PCMSK |= (1<<PIND2);
   MCUCR = (1<<ISC01) | (1<<ISC00);
   GIMSK  |= (1<<INT0);

   // Enable all interrupts
   sei();
}

volatile uint8_t mode = MODE1;

int main(void) {

   init_avr();

   while(1) {
      continue;
   }
}

...my question is: Is there anything I should be doing in that infinite while() loop now that I'm ? I guess is that the 'the way' you do it? It would seem to me that's just adding extra cycle overhead spinning that loop just to keep from breaking out of main()?

December 10, 2012
by JimFrederickson
JimFrederickson's Avatar

When you ask "Is there anything I should be doing in that infinite while() loop", you are doing something something "you are waiting for something that needs to be processed".

Waiting for something that needs to be processed is not "adding extra cycle overhead".

It is simply "waiting".

The Microcontroller is processing millions of instructions per second. What seems like a "single event to you" will often look much different when it's broken into slices of 1,000, 100,000, or even millions.

Anything that you process with your Microcontroller code that is able to be perceived by you in realtime, will need/benefit from some form of "timing" and/or "scaling".

Anytime that something is "timed" or "scaled" then there will be some "waiting around in the code for something that needs to be processed".

I think that your "smoothness issue" only has 3 possibilities: (Assuming that you are not employing some sort of hardware debounce or validation.)

1 - you are not processing the "key press event" properly. Just recognizing an "interrupt" as a "key press event", if that is what you are doing, will not by itself work. (Part of the the discussion in the thread I had pointed you was about "debouncing"/"validation" this could be affecting your results.)

2 - you are processing a "key press event" too quickly. Sometimes changing something immediately when detected is not going to work.

3 - There is a problem with the PWM that you are using, or the choices being used for each of your modes.

(Maybe a hardware problem, but I think that is least likely since it seems to be working at all.)

I think 1 or 2 are the mostly likely problems.

If you are going to use a "key press event", ("button presses"), to select a mode for your PWM then you will need to consider what constitutes a "key press event" and a "key release event". ("timing".)

Or

If you don't want to think about a "key release event" then you will need to consider how long a "key press event" has to be constant before it is determined to be another "key press event". ("timing".)

Sometimes, like in controlling brightness for an LED using PWM, a 50% on duty cycle is not really perceived as being 50% of the brightness. (A similar issue can occur with RGB LEDs when mixing to achieve certain colors...)

So it may be your PWM choices need to be altered a bit. ("scaling".)

Any "scaling" that you use would likely need to be determined by trial and error, and what looks good to you.

December 10, 2012
by JimFrederickson
JimFrederickson's Avatar

Another thing to consider is the "extra cycles that you are using for your while() Loop" means that there are more things that you can do.

For your current program there is only 1 thing that you are doing in your while() Loop, but that doesn't have to be the case.

In future programs there may be more things that you will want to do and they all/each can be added into the loop and processed at the appropriate time.

Programming a project with your Microcontroller can often become a matter of "managing constraints". (As with anything really...)

Your Micrcontroller has 4 main resources that will "constrain" in various ways what you can do:
1 - Clock Cycles (How fast something can be done)
2 - Program Memory (How complex/large your program can be)
3 - Data Memory (How complex/much data you can manage)
4 - I/O Pins (How many things you can control or monitor)

If any program you create uses up any of those resources in their entirety then you have reached the limit of what you can do with your Microcontroller. (There are "other constraints" too, but I think those are more subordinate to these.)

In order to do more you would then need to "free-up" one or more of these resources in order to be able to do that.

Maybe you can be "more efficient" or maybe you will need to add additional "hardware".

None-the-less there is a WHOLE LOT you can do with these chips and they are great for a variety of useful things in addition to learning.

Good Luck...

Post a Reply

Please log in to post a reply.

Did you know that NerdKits make a great parent/child activity? Learn more...